diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..157722f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+build/*
+/buildSrc/build/*
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..e6a7a34
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+charty-library
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..29e1ffa
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..2a4d5b5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..3856dba
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,38 @@
+## Charty : Elementary Chart library for Compose
+
+![Charty](img/charty-banner.png)
+
+Chart Library built using Jetpack Compose and is highly customizable. Updates coming soon!
+_Made with ❤️ for Android Developers by Himanshu_
+
+[![Github Followers](https://img.shields.io/github/followers/hi-manshu?label=Follow&style=social)](https://github.com/hi-manshu)
+[![Twitter Follow](https://img.shields.io/twitter/follow/hi_man_shoe?label=Follow&style=social)](https://twitter.com/hi_man_shoe)
+
+## Implementation
+
+### Gradle setup
+
+In `build.gradle` of app module, include this dependency
+
+```gradle
+dependencies {
+ implementation("com.himanshoe:charty:1.0.0-alpha01")
+}
+```
+
+## Documentation
+You can find the detail implementation of the following:
+
+- [BarChart](docs/BarChart.md)
+- [GroupedBarChart](docs/GroupedBarChart.md)
+- [CircleChart](docs/CircleChart.md)
+- [PointChart](docs/PointChart.md)
+- [LineChart](docs/LineChart.md)
+- [PieChart](docs/PieChart.md)
+- [CurveLineChart](docs/CurveLineChart.md)
+
+
+### Contribution
+Please feel free to fork it and open a PR.
+
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..18e3185
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,62 @@
+plugins {
+ id(Plugins.application)
+ kotlin(Plugins.android)
+}
+
+android {
+ compileSdk = ModuleExtension.compileSdkVersion
+
+ defaultConfig {
+ applicationId = ModuleExtension.App.applicationId
+ minSdk = ModuleExtension.DefaultConfigs.minSdkVersion
+ targetSdk = ModuleExtension.DefaultConfigs.targetSdkVersion
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = ModuleExtension.DefaultConfigs.testInstrumentationRunner
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile(ModuleExtension.DefaultConfigs.defaultProguardOptimizeFileName),
+ ModuleExtension.DefaultConfigs.proGuardRules
+ )
+ }
+ create("staging") {
+ initWith(getByName("debug"))
+ matchingFallbacks.add("debug")
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = ModuleExtension.jvmTarget
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = Versions.compose
+ }
+}
+
+dependencies {
+ //implementation(project(":charty"))
+ implementation("com.himanshoe:charty:1.0.0-alpha01")
+ implementation(Deps.Compose.ui)
+ implementation(Deps.Compose.material)
+ implementation(Deps.Compose.activity)
+ implementation(Deps.Jetpack.Core.ktx)
+ implementation(Deps.Android.materialDesign)
+ testImplementation(Deps.Test.jUnit)
+ androidTestImplementation(Deps.AndroidTest.jUnitExtensions)
+ androidTestImplementation(Deps.AndroidTest.espressoCore)
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..57e3459
--- /dev/null
+++ b/app/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.himanshoe.charty
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.himanshoe.charty", appContext.packageName)
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7c8156f
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/himanshoe/charty/MainActivity.kt b/app/src/main/java/com/himanshoe/charty/MainActivity.kt
new file mode 100644
index 0000000..11df077
--- /dev/null
+++ b/app/src/main/java/com/himanshoe/charty/MainActivity.kt
@@ -0,0 +1,175 @@
+package com.himanshoe.charty
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.himanshoe.charty.bar.BarChart
+import com.himanshoe.charty.bar.GroupedBarChart
+import com.himanshoe.charty.bar.model.BarData
+import com.himanshoe.charty.bar.model.GroupedBarData
+import com.himanshoe.charty.circle.CircleChart
+import com.himanshoe.charty.circle.model.CircleData
+import com.himanshoe.charty.line.CurveLineChart
+import com.himanshoe.charty.line.LineChart
+import com.himanshoe.charty.line.model.LineData
+import com.himanshoe.charty.pie.PieChart
+import com.himanshoe.charty.point.PointChart
+import com.himanshoe.charty.point.model.PointData
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ LazyColumn(Modifier.fillMaxSize()) {
+ item {
+ CurveLineChart(
+ modifier = Modifier
+ .padding(top = 100.dp)
+ .size(width = 500.dp, height = 300.dp)
+ .padding(20.dp),
+ colors = listOf(
+ Color.Green,
+ Color.Black,
+ ),
+ lineData = listOf(
+ LineData(10F, 35F),
+ LineData(20F, 25F),
+ LineData(50F, 100F),
+ LineData(20F, 25F),
+
+ )
+ )
+ }
+ item {
+ BarChart(
+ modifier = Modifier
+ .padding(top = 100.dp)
+ .size(width = 500.dp, height = 300.dp)
+ .padding(20.dp),
+ onBarClick = {
+ },
+ colors = listOf(Color.Green, Color.Black),
+ barData = listOf(
+ BarData(10F, 35F),
+ BarData(20F, 25F),
+ BarData(10F, 50F),
+ BarData(100F, 10F),
+ BarData(10F, 15F),
+ BarData(50F, 100F),
+ BarData(20F, 25F),
+ )
+ )
+ }
+
+ item {
+ GroupedBarChart(
+ modifier = Modifier
+ .size(width = 500.dp, height = 300.dp)
+ .padding(20.dp),
+ groupedBarData = listOf(
+ GroupedBarData(
+ listOf(
+ BarData(10F, 35F),
+ BarData(20F, 25F),
+ BarData(10F, 50F),
+ ),
+ colors = listOf(Color.Black, Color.Green, Color.Yellow)
+ ),
+ GroupedBarData(
+ listOf(
+ BarData(10F, 35F),
+ BarData(20F, 25F),
+ BarData(10F, 50F),
+ ),
+ colors = listOf(Color.Black, Color.Green, Color.Yellow)
+ ),
+ GroupedBarData(
+ listOf(
+ BarData(10F, 35F),
+ BarData(20F, 25F),
+ BarData(10F, 50F),
+ ),
+ colors = listOf(Color.Black, Color.Green, Color.Yellow)
+ ),
+ ),
+ )
+ }
+ item {
+ PieChart(
+ modifier = Modifier
+ .scale(1f)
+ .size(400.dp)
+ .padding(20.dp),
+ data = listOf(20F, 50F, 100F, 70F, 20F, 50F, 100F, 70F),
+ isDonut = true,
+ valueTextColor = Color.Black,
+ onSectionClicked = { percent, value ->
+ }
+ )
+ }
+ item {
+ CircleChart(
+ modifier = Modifier
+ .scale(1f)
+ .size(400.dp)
+ .padding(20.dp),
+ circleData = listOf(
+ CircleData(10F, 235F, Color.Green),
+ CircleData(10F, 135F, Color.Green),
+ CircleData(10F, 315F, Color.Green),
+ CircleData(20F, 50F, Color.Green),
+ CircleData(30F, 315F)
+ ),
+ color = Color.Yellow
+ )
+ }
+
+ item {
+ LineChart(
+ modifier = Modifier
+ .padding(top = 100.dp)
+ .size(width = 500.dp, height = 300.dp)
+ .padding(20.dp),
+ colors = listOf(Color.Green, Color.Black),
+ lineData = listOf(
+ LineData(10F, 35F),
+ LineData(20F, 25F),
+ LineData(10F, 50F),
+ LineData(100F, 10F),
+ LineData(10F, 15F),
+ LineData(50F, 100F),
+ LineData(20F, 25F),
+ )
+ )
+ }
+
+ item {
+ PointChart(
+ modifier = Modifier
+ .padding(top = 100.dp)
+ .size(width = 500.dp, height = 300.dp)
+ .padding(20.dp),
+ colors = listOf(Color.Green, Color.Black),
+ pointData = listOf(
+ PointData(10F, 35F),
+ PointData(20F, 25F),
+ PointData(10F, 50F),
+ PointData(100F, 10F),
+ PointData(10F, 15F),
+ PointData(50F, 100F),
+ PointData(20F, 25F),
+ )
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/himanshoe/charty/ui/theme/Color.kt b/app/src/main/java/com/himanshoe/charty/ui/theme/Color.kt
new file mode 100644
index 0000000..7347736
--- /dev/null
+++ b/app/src/main/java/com/himanshoe/charty/ui/theme/Color.kt
@@ -0,0 +1,8 @@
+package com.himanshoe.charty.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
diff --git a/app/src/main/java/com/himanshoe/charty/ui/theme/Shape.kt b/app/src/main/java/com/himanshoe/charty/ui/theme/Shape.kt
new file mode 100644
index 0000000..3a993af
--- /dev/null
+++ b/app/src/main/java/com/himanshoe/charty/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.himanshoe.charty.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
diff --git a/app/src/main/java/com/himanshoe/charty/ui/theme/Theme.kt b/app/src/main/java/com/himanshoe/charty/ui/theme/Theme.kt
new file mode 100644
index 0000000..a807427
--- /dev/null
+++ b/app/src/main/java/com/himanshoe/charty/ui/theme/Theme.kt
@@ -0,0 +1,47 @@
+package com.himanshoe.charty.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple200,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+ primary = Purple500,
+ primaryVariant = Purple700,
+ secondary = Teal200
+
+ /* Other default colors to override
+ background = Color.White,
+ surface = Color.White,
+ onPrimary = Color.White,
+ onSecondary = Color.Black,
+ onBackground = Color.Black,
+ onSurface = Color.Black,
+ */
+)
+
+@Composable
+fun ChartylibraryTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
diff --git a/app/src/main/java/com/himanshoe/charty/ui/theme/Type.kt b/app/src/main/java/com/himanshoe/charty/ui/theme/Type.kt
new file mode 100644
index 0000000..b2b0490
--- /dev/null
+++ b/app/src/main/java/com/himanshoe/charty/ui/theme/Type.kt
@@ -0,0 +1,28 @@
+package com.himanshoe.charty.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp
+ )
+ /* Other default text styles to override
+ button = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.W500,
+ fontSize = 14.sp
+ ),
+ caption = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp
+ )
+ */
+)
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..7706ab9
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6b78462
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6b78462
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..ca1931b
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..cacd6c0
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ charty-library
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..57d0610
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..148c18b
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..0c4f95c
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/app/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt b/app/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt
new file mode 100644
index 0000000..77df319
--- /dev/null
+++ b/app/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.himanshoe.charty
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..40241ed
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,36 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ maven("https://plugins.gradle.org/m2/")
+
+ }
+ dependencies {
+ classpath(Deps.Gradle.androidGradlePlugin)
+ classpath(Deps.Gradle.kotlinGradlePlugin)
+ classpath(Deps.Gradle.vanniktechGradlePlugin)
+ classpath(Deps.Gradle.detektGradlePlugin)
+ classpath(Deps.Gradle.ktlintGradlePlugin)
+ }
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
+subprojects {
+ apply(plugin = Plugins.ktlint)
+ apply(plugin = Plugins.detekt)
+
+// detekt {
+// config = files("$rootDir/${ModuleExtension.FilePath.detekt}")
+// buildUponDefaultConfig = true
+// }
+}
+allprojects {
+ pluginManager.withPlugin(Plugins.vanniktechPublish) {
+ extensions.getByType(com.vanniktech.maven.publish.MavenPublishPluginExtension::class.java)
+ .apply {
+ sonatypeHost = com.vanniktech.maven.publish.SonatypeHost.S01
+ }
+ }
+}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 0000000..1c0ea9a
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ `kotlin-dsl`
+ kotlin("jvm") version "1.6.10"
+}
+
+repositories {
+ mavenCentral()
+}
diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt
new file mode 100644
index 0000000..5c59aef
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Dependencies.kt
@@ -0,0 +1,57 @@
+object Plugins {
+ val application by lazy { "com.android.application" }
+ val library by lazy { "com.android.library" }
+ val android by lazy { "android" }
+ val kotlinAndroid by lazy { "kotlin-android" }
+ val vanniktechPublish by lazy { "com.vanniktech.maven.publish" }
+ val ktlint by lazy { "org.jlleitschuh.gradle.ktlint" }
+ val detekt by lazy { "io.gitlab.arturbosch.detekt" }
+}
+
+object Deps {
+
+ object Compose {
+ val ui by lazy { "androidx.compose.ui:ui:${Versions.compose}" }
+ val uiTooling by lazy { "androidx.compose.ui:ui-tooling:${Versions.compose}" }
+ val uiToolingPreview by lazy { "androidx.compose.ui:ui-tooling-preview:${Versions.compose}" }
+ val material by lazy { "androidx.compose.material:material:${Versions.compose}" }
+ val activity by lazy { "androidx.activity:activity-compose:${Versions.activity}" }
+ val viewmodel by lazy { "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1" }
+ }
+
+ object Android {
+ val materialDesign by lazy { "com.google.android.material:material:${Versions.material}" }
+ }
+
+ object Test {
+ val jUnit by lazy { "junit:junit:${Versions.jUnit}" }
+ }
+
+ object AndroidTest {
+ val jUnitExtensions by lazy { "androidx.test.ext:junit:${Versions.jUnitExtensions}" }
+ val espressoCore by lazy { "androidx.test.espresso:espresso-core:${Versions.espresso}" }
+ val uiTestJunit by lazy { "androidx.compose.ui:ui-test-junit4:${Versions.compose}" }
+ }
+
+ object DateTime {
+ val date by lazy { "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" }
+ }
+
+ object Jetpack {
+ object Core {
+ val ktx by lazy { "androidx.core:core-ktx:${Versions.core}" }
+ }
+ }
+
+ object Desugar {
+ val jdk by lazy { "com.android.tools:desugar_jdk_libs:1.1.5" }
+ }
+
+ object Gradle {
+ val kotlinGradlePlugin by lazy { "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" }
+ val androidGradlePlugin by lazy { "com.android.tools.build:gradle:${Versions.androidGradlePlugin}" }
+ val vanniktechGradlePlugin by lazy { "com.vanniktech:gradle-maven-publish-plugin:${Versions.vanniktechGradlePlugin}" }
+ val ktlintGradlePlugin by lazy { "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlint}" }
+ val detektGradlePlugin by lazy { "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${Versions.detekt}" }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/ModuleExtension.kt b/buildSrc/src/main/kotlin/ModuleExtension.kt
new file mode 100644
index 0000000..1f30169
--- /dev/null
+++ b/buildSrc/src/main/kotlin/ModuleExtension.kt
@@ -0,0 +1,22 @@
+object ModuleExtension {
+ const val compileSdkVersion = 32
+ const val jvmTarget = "11"
+
+ object DefaultConfigs {
+ const val minSdkVersion = 21
+ const val targetSdkVersion = 32
+ const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ const val defaultConsumerProguardFiles = "consumer-rules.pro"
+ const val proGuardRules = "proguard-rules.pro"
+ const val defaultProguardOptimizeFileName = "proguard-android-optimize.txt"
+ }
+
+ object App {
+ const val applicationId = "com.himanshoe.sample"
+ }
+
+ object FilePath {
+ const val gitHooks = "gradle/scripts/git-hooks.gradle.kts"
+ const val detekt = "gradle/config/detekt.yml"
+ }
+}
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
new file mode 100644
index 0000000..72331b1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -0,0 +1,14 @@
+object Versions {
+ const val compose = "1.2.0"
+ const val androidGradlePlugin = "7.2.0"
+ const val vanniktechGradlePlugin = "0.18.0"
+ const val kotlin = "1.7.0"
+ const val activity = "1.3.1"
+ const val material = "1.4.0"
+ const val jUnit = "4.13.2"
+ const val jUnitExtensions = "1.1.3"
+ const val espresso = "3.4.0"
+ const val core = "1.7.0"
+ const val ktlint = "10.2.0"
+ const val detekt = "1.19.0"
+}
diff --git a/charty/.gitignore b/charty/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/charty/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/charty/build.gradle.kts b/charty/build.gradle.kts
new file mode 100644
index 0000000..4507c20
--- /dev/null
+++ b/charty/build.gradle.kts
@@ -0,0 +1,55 @@
+plugins {
+ id(Plugins.library)
+ id(Plugins.kotlinAndroid)
+}
+
+android {
+ compileSdk = ModuleExtension.compileSdkVersion
+
+ defaultConfig {
+ minSdk = ModuleExtension.DefaultConfigs.minSdkVersion
+ targetSdk = ModuleExtension.DefaultConfigs.targetSdkVersion
+
+ testInstrumentationRunner = ModuleExtension.DefaultConfigs.testInstrumentationRunner
+ consumerProguardFiles(ModuleExtension.DefaultConfigs.defaultConsumerProguardFiles)
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile(ModuleExtension.DefaultConfigs.defaultProguardOptimizeFileName),
+ ModuleExtension.DefaultConfigs.proGuardRules
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = ModuleExtension.jvmTarget
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = Versions.compose
+ }
+}
+
+dependencies {
+ implementation(Deps.Compose.ui)
+ implementation(Deps.Compose.material)
+ implementation(Deps.Compose.uiToolingPreview)
+ implementation(Deps.Compose.activity)
+
+ debugApi(Deps.Compose.uiTooling)
+
+ testImplementation(Deps.Test.jUnit)
+ androidTestImplementation(Deps.AndroidTest.jUnitExtensions)
+ androidTestImplementation(Deps.AndroidTest.espressoCore)
+ androidTestApi(Deps.AndroidTest.uiTestJunit)
+}
+
+plugins.apply(Plugins.vanniktechPublish)
diff --git a/charty/consumer-rules.pro b/charty/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/charty/gradle.properties b/charty/gradle.properties
new file mode 100644
index 0000000..8672943
--- /dev/null
+++ b/charty/gradle.properties
@@ -0,0 +1,17 @@
+# Maven
+POM_ARTIFACT_ID=charty
+POM_NAME=charty
+POM_DESCRIPTION=An Elementary Compose Chart library.
+POM_PACKAGING=aar
+POM_INCEPTION_YEAR=2022
+GROUP=com.himanshoe
+VERSION_NAME=1.0.0-alpha01
+VERSION_CODE=1
+POM_URL=https://github.com/hi-manshu
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+POM_SCM_URL=https://github.com/hi-manshu
+POM_DEVELOPER_ID=hi-manshu
+POM_DEVELOPER_NAME=Himanshu Singh
+POM_DEVELOPER_URL=https://github.com/hi-manshu
diff --git a/charty/proguard-rules.pro b/charty/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/charty/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/charty/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt b/charty/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..d223975
--- /dev/null
+++ b/charty/src/androidTest/java/com/himanshoe/charty/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.himanshoe.charty
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.himanshoe.charty.test", appContext.packageName)
+ }
+}
diff --git a/charty/src/main/AndroidManifest.xml b/charty/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5cc4e17
--- /dev/null
+++ b/charty/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/BarChart.kt b/charty/src/main/java/com/himanshoe/charty/bar/BarChart.kt
new file mode 100644
index 0000000..fcc0c8b
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/BarChart.kt
@@ -0,0 +1,108 @@
+package com.himanshoe.charty.bar
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import com.himanshoe.charty.bar.common.calculations.getTopLeft
+import com.himanshoe.charty.bar.common.calculations.getTopRight
+import com.himanshoe.charty.bar.common.component.drawBarLabel
+import com.himanshoe.charty.bar.config.BarConfig
+import com.himanshoe.charty.bar.config.BarConfigDefaults
+import com.himanshoe.charty.bar.model.BarData
+import com.himanshoe.charty.bar.model.maxYValue
+import com.himanshoe.charty.common.axis.AxisConfig
+import com.himanshoe.charty.common.axis.AxisConfigDefaults
+import com.himanshoe.charty.common.axis.xAxis
+import com.himanshoe.charty.common.axis.yAxis
+import com.himanshoe.charty.common.dimens.ChartDimens
+import com.himanshoe.charty.common.dimens.ChartDimensDefaults
+
+@Composable
+fun BarChart(
+ barData: List,
+ color: Color,
+ onBarClick: (BarData) -> Unit,
+ modifier: Modifier = Modifier,
+ barDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ barConfig: BarConfig = BarConfigDefaults.barConfigDimesDefaults()
+) {
+ BarChart(
+ barData = barData,
+ colors = listOf(color, color),
+ onBarClick = onBarClick,
+ modifier = modifier,
+ barDimens = barDimens,
+ axisConfig = axisConfig,
+ barConfig = barConfig
+ )
+}
+
+@Composable
+fun BarChart(
+ barData: List,
+ colors: List,
+ onBarClick: (BarData) -> Unit,
+ modifier: Modifier = Modifier,
+ barDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ barConfig: BarConfig = BarConfigDefaults.barConfigDimesDefaults()
+) {
+
+ val maxYValueState = rememberSaveable { mutableStateOf(barData.maxYValue()) }
+ val clickedBar = remember {
+ mutableStateOf(Offset(-10F, -10F))
+ }
+
+ val maxYValue = maxYValueState.value
+ val barWidth = remember { mutableStateOf(0F) }
+
+ Canvas(
+ modifier = modifier
+ .drawBehind {
+ if (axisConfig.showAxes) {
+ xAxis(axisConfig, maxYValue)
+ yAxis(axisConfig)
+ }
+ }
+ .padding(horizontal = barDimens.horizontalPadding)
+ .pointerInput(Unit) {
+ detectTapGestures(onPress = { offset ->
+ clickedBar.value = offset
+ })
+ }
+ ) {
+ barWidth.value = size.width.div(barData.count().times(1.2F))
+ val yScalableFactor = size.height.div(maxYValue)
+
+ barData.forEachIndexed { index, data ->
+ val topLeft = getTopLeft(index, barWidth, size, data, yScalableFactor)
+ val topRight = getTopRight(index, barWidth, size, data, yScalableFactor)
+ val barHeight = data.yValue.times(yScalableFactor)
+
+ if (clickedBar.value.x in (topLeft.x..topRight.x)) {
+ onBarClick(data)
+ }
+ drawRoundRect(
+ cornerRadius = CornerRadius(if (barConfig.hasRoundedCorner) barHeight else 0F),
+ topLeft = topLeft,
+ brush = Brush.linearGradient(colors),
+ size = Size(barWidth.value, barHeight)
+ )
+ // draw label
+ drawBarLabel(data, barWidth.value, barHeight, topLeft)
+ }
+ }
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/GroupedBarChart.kt b/charty/src/main/java/com/himanshoe/charty/bar/GroupedBarChart.kt
new file mode 100644
index 0000000..8a702e2
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/GroupedBarChart.kt
@@ -0,0 +1,91 @@
+package com.himanshoe.charty.bar
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import com.himanshoe.charty.bar.common.calculations.getTopLeft
+import com.himanshoe.charty.bar.common.calculations.getTopRight
+import com.himanshoe.charty.bar.common.component.drawBarLabel
+import com.himanshoe.charty.bar.config.BarConfig
+import com.himanshoe.charty.bar.config.BarConfigDefaults
+import com.himanshoe.charty.bar.model.BarData
+import com.himanshoe.charty.bar.model.GroupedBarData
+import com.himanshoe.charty.bar.model.maxYValue
+import com.himanshoe.charty.bar.model.totalItems
+import com.himanshoe.charty.common.axis.AxisConfig
+import com.himanshoe.charty.common.axis.AxisConfigDefaults
+import com.himanshoe.charty.common.axis.xAxis
+import com.himanshoe.charty.common.axis.yAxis
+import com.himanshoe.charty.common.dimens.ChartDimens
+import com.himanshoe.charty.common.dimens.ChartDimensDefaults
+
+@Composable
+fun GroupedBarChart(
+ groupedBarData: List,
+ modifier: Modifier = Modifier,
+ onBarClick: (BarData) -> Unit = {},
+ barDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ barConfig: BarConfig = BarConfigDefaults.barConfigDimesDefaults()
+) {
+ val barWidth = remember { mutableStateOf(0F) }
+ val maxYValueState = rememberSaveable { mutableStateOf(groupedBarData.maxYValue()) }
+ val clickedBar = remember {
+ mutableStateOf(Offset(-10F, -10F))
+ }
+ val maxYValue = maxYValueState.value
+
+ val totalItems: Int = groupedBarData.totalItems()
+ Canvas(
+ modifier = modifier
+ .drawBehind {
+ if (axisConfig.showAxes) {
+ xAxis(axisConfig, maxYValue)
+ yAxis(axisConfig)
+ }
+ }
+ .padding(horizontal = barDimens.horizontalPadding)
+ .pointerInput(Unit) {
+ detectTapGestures(onPress = { offset ->
+ clickedBar.value = offset
+ })
+ }
+ ) {
+ barWidth.value = size.width.div(totalItems.times(1.2F))
+ val yScalableFactor = size.height.div(maxYValue)
+ val groupedBarDataColor: List = groupedBarData.flatMap { it.colors }
+ val groupedBarDataCount = groupedBarData.flatMap { it.barData }.count()
+
+ if (groupedBarDataColor.count() != groupedBarDataCount) throw Exception("Total colors cannot be more then $groupedBarDataCount")
+
+ groupedBarData.flatMap { it.barData }
+ .forEachIndexed { index, data ->
+ val topLeft = getTopLeft(index, barWidth, size, data, yScalableFactor)
+ val topRight = getTopRight(index, barWidth, size, data, yScalableFactor)
+ val barHeight = data.yValue.times(yScalableFactor)
+
+ if (clickedBar.value.x in (topLeft.x..topRight.x)) {
+ onBarClick(data)
+ }
+ drawRoundRect(
+ cornerRadius = CornerRadius(if (barConfig.hasRoundedCorner) barHeight else 0F),
+ topLeft = topLeft,
+ color = groupedBarDataColor[index],
+ size = Size(barWidth.value, barHeight)
+ )
+ // draw label
+ drawBarLabel(data, barWidth.value, barHeight, topLeft)
+ }
+ }
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/common/calculations/BarCalculations.kt b/charty/src/main/java/com/himanshoe/charty/bar/common/calculations/BarCalculations.kt
new file mode 100644
index 0000000..ff01734
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/common/calculations/BarCalculations.kt
@@ -0,0 +1,28 @@
+package com.himanshoe.charty.bar.common.calculations
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import com.himanshoe.charty.bar.model.BarData
+
+internal fun getTopLeft(
+ index: Int,
+ barWidth: MutableState,
+ size: Size,
+ barData: BarData,
+ yScalableFactor: Float
+) = Offset(
+ x = index.times(barWidth.value.times(1.2F)),
+ y = size.height.minus(barData.yValue.times(yScalableFactor))
+)
+
+internal fun getTopRight(
+ index: Int,
+ barWidth: MutableState,
+ size: Size,
+ barData: BarData,
+ yChunck: Float
+) = Offset(
+ x = index.plus(1).times(barWidth.value.times(1.2F)),
+ y = size.height.minus(barData.yValue.times(yChunck))
+)
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/common/component/Labels.kt b/charty/src/main/java/com/himanshoe/charty/bar/common/component/Labels.kt
new file mode 100644
index 0000000..e85daaf
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/common/component/Labels.kt
@@ -0,0 +1,29 @@
+package com.himanshoe.charty.bar.common.component
+
+import android.graphics.Paint
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import com.himanshoe.charty.bar.model.BarData
+
+internal fun DrawScope.drawBarLabel(
+ data: BarData,
+ barWidth: Float,
+ barHeight: Float,
+ topLeft: Offset
+) {
+ drawIntoCanvas {
+ it.nativeCanvas.apply {
+ drawText(
+ data.xValue.toString(),
+ topLeft.x.plus(barWidth.div(2)),
+ topLeft.y.plus(barHeight.plus(barWidth.div(2))),
+ Paint().apply {
+ textSize = size.width.div(30)
+ textAlign = Paint.Align.CENTER
+ }
+ )
+ }
+ }
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/config/BarConfig.kt b/charty/src/main/java/com/himanshoe/charty/bar/config/BarConfig.kt
new file mode 100644
index 0000000..5318923
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/config/BarConfig.kt
@@ -0,0 +1,12 @@
+package com.himanshoe.charty.bar.config
+
+data class BarConfig(
+ val hasRoundedCorner: Boolean = false
+)
+
+internal object BarConfigDefaults {
+
+ fun barConfigDimesDefaults() = BarConfig(
+ hasRoundedCorner = false
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/model/BarData.kt b/charty/src/main/java/com/himanshoe/charty/bar/model/BarData.kt
new file mode 100644
index 0000000..35e18dc
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/model/BarData.kt
@@ -0,0 +1,7 @@
+package com.himanshoe.charty.bar.model
+
+data class BarData(val xValue: Any, val yValue: Float)
+
+internal fun List.maxYValue() = maxOf {
+ it.yValue
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/bar/model/GroupedBarData.kt b/charty/src/main/java/com/himanshoe/charty/bar/model/GroupedBarData.kt
new file mode 100644
index 0000000..9db5251
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/bar/model/GroupedBarData.kt
@@ -0,0 +1,13 @@
+package com.himanshoe.charty.bar.model
+
+import androidx.compose.ui.graphics.Color
+
+data class GroupedBarData(val barData: List, val colors: List = List(barData.count()) { Color.Transparent })
+
+internal fun List.totalItems(): Int = this.sumOf {
+ it.barData.count()
+}
+
+internal fun List.maxYValue() = maxOf {
+ it.barData.maxYValue()
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/circle/CircleChart.kt b/charty/src/main/java/com/himanshoe/charty/circle/CircleChart.kt
new file mode 100644
index 0000000..877a76f
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/circle/CircleChart.kt
@@ -0,0 +1,88 @@
+package com.himanshoe.charty.circle
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.Stroke
+import com.himanshoe.charty.circle.config.CircleConfig
+import com.himanshoe.charty.circle.config.CircleConfigDefaults
+import com.himanshoe.charty.circle.model.CircleData
+import com.himanshoe.charty.circle.model.maxYValue
+
+@Composable
+fun CircleChart(
+ circleData: List,
+ modifier: Modifier = Modifier,
+ color: Color,
+ isAnimated: Boolean = true,
+ config: CircleConfig = CircleConfigDefaults.circleConfigDefaults()
+) {
+ CircleChart(
+ circleData = circleData,
+ colors = listOf(color, color),
+ modifier = modifier,
+ config = config,
+ isAnimated = isAnimated,
+ )
+}
+
+@Composable
+fun CircleChart(
+ circleData: List,
+ modifier: Modifier = Modifier,
+ colors: List,
+ config: CircleConfig = CircleConfigDefaults.circleConfigDefaults(),
+ isAnimated: Boolean
+) {
+ val maxYValueState = rememberSaveable { mutableStateOf(circleData.maxYValue()) }
+ val maxYValue = maxYValueState.value
+ val angleFactor = if (config.maxValue != null) 360.div(config.maxValue) else 360.div(maxYValue)
+
+ val animatedFactor = remember {
+ Animatable(initialValue = 0f)
+ }
+
+ LaunchedEffect(key1 = true) {
+ animatedFactor.animateTo(
+ targetValue = angleFactor,
+ animationSpec = tween(1000)
+ )
+ }
+
+ Canvas(modifier = modifier) {
+ val scaleFactor = size.width.div(circleData.count())
+ val sizeArc = size.div(scaleFactor)
+
+ circleData.forEachIndexed { index, circleData ->
+ val circleColor: List =
+ if (circleData.color != null) listOf(circleData.color, circleData.color) else colors
+ val arcWidth = sizeArc.width.plus(index.times(scaleFactor))
+ val arcHeight = sizeArc.height.plus(index.times(scaleFactor))
+ val factor = if (isAnimated) animatedFactor.value else angleFactor
+
+ drawArc(
+ brush = Brush.linearGradient(circleColor),
+ startAngle = config.startPosition.angle,
+ sweepAngle = factor.times(circleData.yValue),
+ topLeft = Offset(
+ (size.width - arcWidth).div(2f),
+ (size.height - arcHeight).div(2f)
+ ),
+ useCenter = false,
+ style = Stroke(width = scaleFactor.div(2.5F), cap = StrokeCap.Round),
+ size = Size(arcWidth, arcHeight)
+ )
+ }
+ }
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/circle/config/CircleConfig.kt b/charty/src/main/java/com/himanshoe/charty/circle/config/CircleConfig.kt
new file mode 100644
index 0000000..13494cf
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/circle/config/CircleConfig.kt
@@ -0,0 +1,14 @@
+package com.himanshoe.charty.circle.config
+
+data class CircleConfig(
+ val startPosition: StartPosition = StartPosition.Top,
+ val maxValue: Float?
+)
+
+internal object CircleConfigDefaults {
+
+ fun circleConfigDefaults() = CircleConfig(
+ startPosition = StartPosition.Custom(30F),
+ maxValue = null,
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/circle/config/StartPosition.kt b/charty/src/main/java/com/himanshoe/charty/circle/config/StartPosition.kt
new file mode 100644
index 0000000..189375b
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/circle/config/StartPosition.kt
@@ -0,0 +1,9 @@
+package com.himanshoe.charty.circle.config
+
+sealed class StartPosition(val angle: Float) {
+ object Top : StartPosition(270F)
+ object Right : StartPosition(0F)
+ object Bottom : StartPosition(900F)
+ object End : StartPosition(180F)
+ data class Custom(val customAngle: Float) : StartPosition(customAngle)
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/circle/model/CircleData.kt b/charty/src/main/java/com/himanshoe/charty/circle/model/CircleData.kt
new file mode 100644
index 0000000..343159d
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/circle/model/CircleData.kt
@@ -0,0 +1,9 @@
+package com.himanshoe.charty.circle.model
+
+import androidx.compose.ui.graphics.Color
+
+data class CircleData(val xValue: Any, val yValue: Float, val color: Color? = null)
+
+fun List.maxYValue() = maxOf {
+ it.yValue
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/common/axis/Axis.kt b/charty/src/main/java/com/himanshoe/charty/common/axis/Axis.kt
new file mode 100644
index 0000000..d6376c6
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/common/axis/Axis.kt
@@ -0,0 +1,58 @@
+package com.himanshoe.charty.common.axis
+
+import android.graphics.Paint
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import java.text.DecimalFormat
+
+internal fun DrawScope.xAxis(axisConfig: AxisConfig, maxValue: Float) {
+ val graphYAxisEndPoint = size.height.div(4)
+ val pathEffect = PathEffect.dashPathEffect(floatArrayOf(40f, 20f), 0f)
+ val labelChunck = maxValue.div(4)
+
+ repeat(5) { index ->
+ val yAxisEndPoint = graphYAxisEndPoint.times(index)
+ if (axisConfig.showUnitLabels) {
+ drawIntoCanvas {
+ it.nativeCanvas.apply {
+ drawText(
+ getLabelText(labelChunck.times(4.minus(index))),
+ 0F.minus(25),
+ yAxisEndPoint.minus(10),
+ Paint().apply {
+ textSize = size.width.div(30)
+ textAlign = Paint.Align.CENTER
+ }
+ )
+ }
+ }
+ }
+ if (index != 0) {
+ drawLine(
+ start = Offset(x = 0f, y = yAxisEndPoint),
+ end = Offset(x = size.width, y = yAxisEndPoint),
+ color = axisConfig.xAxisColor,
+ pathEffect = pathEffect,
+ alpha = 0.2F,
+ strokeWidth = size.width.div(200)
+ )
+ }
+ }
+}
+
+internal fun DrawScope.yAxis(axisConfig: AxisConfig) {
+ val pathEffect = PathEffect.dashPathEffect(floatArrayOf(40f, 20f), 0f)
+ drawLine(
+ start = Offset(x = 0f, y = 0F),
+ end = Offset(x = 0F, y = size.height),
+ color = axisConfig.yAxisColor,
+ pathEffect = pathEffect,
+ alpha = 0.2F,
+ strokeWidth = size.width.div(200)
+ )
+}
+
+private fun getLabelText(value: Float) = DecimalFormat("#.##").format(value).toString()
diff --git a/charty/src/main/java/com/himanshoe/charty/common/axis/AxisConfig.kt b/charty/src/main/java/com/himanshoe/charty/common/axis/AxisConfig.kt
new file mode 100644
index 0000000..23a3871
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/common/axis/AxisConfig.kt
@@ -0,0 +1,20 @@
+package com.himanshoe.charty.common.axis
+
+import androidx.compose.ui.graphics.Color
+
+data class AxisConfig(
+ val showAxes: Boolean,
+ val showUnitLabels: Boolean,
+ val xAxisColor: Color = Color.LightGray,
+ val yAxisColor: Color = Color.LightGray,
+)
+
+internal object AxisConfigDefaults {
+
+ fun axisConfigDefaults() = AxisConfig(
+ xAxisColor = Color.LightGray,
+ showAxes = true,
+ showUnitLabels = true,
+ yAxisColor = Color.LightGray,
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/common/dimens/ChartDimens.kt b/charty/src/main/java/com/himanshoe/charty/common/dimens/ChartDimens.kt
new file mode 100644
index 0000000..e843d0b
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/common/dimens/ChartDimens.kt
@@ -0,0 +1,15 @@
+package com.himanshoe.charty.common.dimens
+
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+data class ChartDimens(
+ val horizontalPadding: Dp
+)
+
+internal object ChartDimensDefaults {
+
+ fun chartDimesDefaults() = ChartDimens(
+ horizontalPadding = 4.dp
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/line/CurveLineChart.kt b/charty/src/main/java/com/himanshoe/charty/line/CurveLineChart.kt
new file mode 100644
index 0000000..776af05
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/line/CurveLineChart.kt
@@ -0,0 +1,191 @@
+package com.himanshoe.charty.line
+
+import android.graphics.PointF
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.asAndroidPath
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.drawscope.Stroke
+import com.himanshoe.charty.common.axis.AxisConfig
+import com.himanshoe.charty.common.axis.AxisConfigDefaults
+import com.himanshoe.charty.common.axis.xAxis
+import com.himanshoe.charty.common.axis.yAxis
+import com.himanshoe.charty.common.dimens.ChartDimens
+import com.himanshoe.charty.common.dimens.ChartDimensDefaults
+import com.himanshoe.charty.line.config.CurveLineConfig
+import com.himanshoe.charty.line.config.CurveLineConfigDefaults
+import com.himanshoe.charty.line.model.LineData
+import com.himanshoe.charty.line.model.maxYValue
+
+@Composable
+fun CurveLineChart(
+ lineData: List,
+ color: Color,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ curveLineConfig: CurveLineConfig = CurveLineConfigDefaults.curveLineConfigDefaults()
+) {
+ CurveLineChart(
+ modifier = modifier,
+ lineData = lineData,
+ colors = listOf(color, color),
+ chartDimens = chartDimens,
+ axisConfig = axisConfig,
+ curveLineConfig = curveLineConfig
+ )
+}
+
+@Composable
+fun CurveLineChart(
+ lineData: List,
+ colors: List,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ curveLineConfig: CurveLineConfig = CurveLineConfigDefaults.curveLineConfigDefaults()
+) {
+ val graphPathPoints = mutableListOf()
+ val backgroundPathPoints = mutableListOf()
+ val lineBound = remember { mutableStateOf(0F) }
+ val maxYValueState = rememberSaveable { mutableStateOf(lineData.maxYValue()) }
+ val maxYValue = maxYValueState.value
+
+ Canvas(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(horizontal = chartDimens.horizontalPadding)
+ .drawBehind {
+ if (axisConfig.showAxes) {
+ xAxis(axisConfig, maxYValue)
+ yAxis(axisConfig)
+ }
+ },
+ onDraw = {
+ val xScaleFactor = size.width.div(lineData.size)
+ val yScaleFactor = size.height.div(maxYValue)
+ val canvasSize = size
+ val radius = size.width.div(70)
+
+ lineBound.value = size.width.div(lineData.count().times(1.2F))
+
+ val lineDataItems: List = lineData.mapIndexed { index, data ->
+ dataToOffSet(
+ index = index,
+ bound = lineBound.value,
+ size = size,
+ data = data,
+ scaleFactor = yScaleFactor
+ )
+ }.toMutableList().also {
+ it.add(Offset(canvasSize.width, canvasSize.height))
+ }
+ val offsetItems: List = mutableListOf().apply {
+ add(Offset(0f, canvasSize.height))
+ addAll(lineDataItems)
+ }
+
+ val xValues = offsetItems.map { it.x }
+ val pointsPath = Path()
+ offsetItems.forEachIndexed { index, offset ->
+ val canDrawCircle = curveLineConfig.hasDotMarker && index != 0 && index != offsetItems.size.minus(1)
+ if (canDrawCircle) {
+ drawCircle(
+ color = curveLineConfig.dotColor,
+ radius = radius,
+ center = Offset(offset.x, offset.y)
+ )
+ }
+ if (index > 0) {
+ storePoints(
+ graphPathPoints,
+ backgroundPathPoints,
+ offset,
+ offsetItems[index.minus(1)]
+ )
+ }
+ }
+
+ pointsPath.apply {
+ reset()
+ moveTo(offsetItems.first().x, offsetItems.first().y)
+ (0.until(offsetItems.size .minus(1))).forEach { index ->
+ cubicTo(
+ graphPathPoints[index].x, graphPathPoints[index].y,
+ backgroundPathPoints[index].x, backgroundPathPoints[index].y,
+ offsetItems[index.plus(1)].x, offsetItems[index.plus(1)].y
+ )
+ }
+ }
+
+ val backgroundPath = android.graphics.Path(pointsPath.asAndroidPath())
+ .asComposePath()
+ .apply {
+ lineTo(xScaleFactor.times(xValues.last()), size.height.minus(yScaleFactor))
+ lineTo(xScaleFactor, size.height - yScaleFactor)
+ close()
+ }
+ drawPath(
+ path = backgroundPath,
+ brush = Brush.verticalGradient(
+ colors = colors,
+ endY = size.height - yScaleFactor
+ ),
+ )
+ drawPath(
+ path = pointsPath,
+ color = Color.Black,
+ style = Stroke(
+ width = 5F,
+ cap = StrokeCap.Round
+ )
+ )
+ }
+ )
+}
+
+private fun storePoints(
+ controlPoints1: MutableList,
+ controlPoints2: MutableList,
+ firstOffset: Offset,
+ previousOffset: Offset
+) {
+ controlPoints1.add(
+ PointF(
+ (firstOffset.x + previousOffset.x) / 2,
+ previousOffset.y
+ )
+ )
+ controlPoints2.add(
+ PointF(
+ (firstOffset.x + previousOffset.x) / 2,
+ firstOffset.y
+ )
+ )
+}
+
+private fun dataToOffSet(
+ index: Int,
+ bound: Float,
+ size: Size,
+ data: LineData,
+ scaleFactor: Float,
+): Offset {
+ val startX = index.times(bound.times(1.2F))
+ val endX = index.plus(1).times(bound.times(1.2F))
+ val y = size.height.minus(data.yValue.times(scaleFactor))
+ return Offset(((startX.plus(endX)).div(2F)), y)
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/line/LineChart.kt b/charty/src/main/java/com/himanshoe/charty/line/LineChart.kt
new file mode 100644
index 0000000..a5203a8
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/line/LineChart.kt
@@ -0,0 +1,147 @@
+package com.himanshoe.charty.line
+
+import android.graphics.Paint
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import com.himanshoe.charty.common.axis.AxisConfig
+import com.himanshoe.charty.common.axis.AxisConfigDefaults
+import com.himanshoe.charty.common.axis.xAxis
+import com.himanshoe.charty.common.axis.yAxis
+import com.himanshoe.charty.common.dimens.ChartDimens
+import com.himanshoe.charty.common.dimens.ChartDimensDefaults
+import com.himanshoe.charty.line.config.LineConfig
+import com.himanshoe.charty.line.config.LineConfigDefaults
+import com.himanshoe.charty.line.model.LineData
+import com.himanshoe.charty.line.model.maxYValue
+
+@Composable
+fun LineChart(
+ lineData: List,
+ color: Color,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ lineConfig: LineConfig = LineConfigDefaults.lineConfigDefaults()
+) {
+ LineChart(
+ lineData = lineData,
+ colors = listOf(color, color),
+ modifier = modifier,
+ chartDimens = chartDimens,
+ axisConfig = axisConfig,
+ lineConfig = lineConfig
+ )
+}
+
+@Composable
+fun LineChart(
+ lineData: List,
+ colors: List,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ lineConfig: LineConfig = LineConfigDefaults.lineConfigDefaults()
+) {
+ val maxYValueState = rememberSaveable { mutableStateOf(lineData.maxYValue()) }
+ val maxYValue = maxYValueState.value
+ val lineBound = remember { mutableStateOf(0F) }
+
+ Canvas(
+ modifier = modifier
+ .drawBehind {
+ if (axisConfig.showAxes) {
+ xAxis(axisConfig, maxYValue)
+ yAxis(axisConfig)
+ }
+ }
+ .padding(horizontal = chartDimens.horizontalPadding)
+
+ ) {
+ lineBound.value = size.width.div(lineData.count().times(1.2F))
+ val scaleFactor = size.height.div(maxYValue)
+ val brush = Brush.linearGradient(colors)
+ val radius = size.width.div(70)
+ val strokeWidth = size.width.div(100)
+ val path = Path().apply {
+ moveTo(0f, size.height)
+ }
+
+ val lastIndex = lineData.size - 1
+ lineData.forEachIndexed { index, data ->
+ val centerOffset = dataToOffSet(index, lineBound.value, size, data, scaleFactor)
+ val drawnPath = path.lineTo(centerOffset.x, centerOffset.y)
+ when (index) {
+ lastIndex -> drawnPath.also {
+ path.lineTo(size.width, size.height)
+ }
+ else -> drawnPath
+ }
+ if (lineConfig.hasDotMarker) {
+ drawCircle(
+ center = centerOffset,
+ radius = radius,
+ brush = brush
+ )
+ }
+ drawXLabel(data, centerOffset, radius)
+ }
+ val stroke = if (lineConfig.hasSmoothCurve) {
+ Stroke(
+ width = strokeWidth,
+ pathEffect = PathEffect.cornerPathEffect(strokeWidth)
+ )
+ } else {
+ Stroke(width = strokeWidth)
+ }
+ drawPath(
+ path = path,
+ brush = brush,
+ style = stroke,
+ )
+ }
+}
+
+private fun DrawScope.drawXLabel(data: LineData, centerOffset: Offset, radius: Float) {
+ drawIntoCanvas {
+ it.nativeCanvas.apply {
+ drawText(
+ data.xValue.toString(),
+ centerOffset.x,
+ size.height.plus(radius.times(4)),
+ Paint().apply {
+ textSize = size.width.div(30)
+ textAlign = Paint.Align.CENTER
+ }
+ )
+ }
+ }
+}
+
+private fun dataToOffSet(
+ index: Int,
+ bound: Float,
+ size: Size,
+ data: LineData,
+ yChunck: Float
+): Offset {
+ val startX = index.times(bound.times(1.2F))
+ val endX = index.plus(1).times(bound.times(1.2F))
+ val y = size.height.minus(data.yValue.times(yChunck))
+ return Offset(((startX.plus(endX)).div(2F)), y)
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/line/config/CurveLineConfig.kt b/charty/src/main/java/com/himanshoe/charty/line/config/CurveLineConfig.kt
new file mode 100644
index 0000000..90f9963
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/line/config/CurveLineConfig.kt
@@ -0,0 +1,16 @@
+package com.himanshoe.charty.line.config
+
+import androidx.compose.ui.graphics.Color
+
+data class CurveLineConfig(
+ val hasDotMarker: Boolean,
+ val dotColor: Color,
+)
+
+internal object CurveLineConfigDefaults {
+
+ fun curveLineConfigDefaults() = CurveLineConfig(
+ hasDotMarker = true,
+ dotColor = Color.Green
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/line/config/LineConfig.kt b/charty/src/main/java/com/himanshoe/charty/line/config/LineConfig.kt
new file mode 100644
index 0000000..9d25ae8
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/line/config/LineConfig.kt
@@ -0,0 +1,14 @@
+package com.himanshoe.charty.line.config
+
+data class LineConfig(
+ val hasSmoothCurve: Boolean = true,
+ val hasDotMarker: Boolean
+)
+
+internal object LineConfigDefaults {
+
+ fun lineConfigDefaults() = LineConfig(
+ hasSmoothCurve = true,
+ hasDotMarker = true
+ )
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/line/model/LineData.kt b/charty/src/main/java/com/himanshoe/charty/line/model/LineData.kt
new file mode 100644
index 0000000..406469e
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/line/model/LineData.kt
@@ -0,0 +1,7 @@
+package com.himanshoe.charty.line.model
+
+data class LineData(val xValue: Any, val yValue: Float)
+
+fun List.maxYValue() = maxOf {
+ it.yValue
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/pie/PieChart.kt b/charty/src/main/java/com/himanshoe/charty/pie/PieChart.kt
new file mode 100644
index 0000000..9192901
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/pie/PieChart.kt
@@ -0,0 +1,185 @@
+package com.himanshoe.charty.pie
+
+import android.graphics.Paint
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.unit.dp
+import com.himanshoe.charty.pie.config.PieConfig
+import com.himanshoe.charty.pie.config.PieConfigDefaults
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+@Composable
+fun PieChart(
+ data: List,
+ modifier: Modifier = Modifier,
+ config: PieConfig = PieConfigDefaults.pieConfigDefaults(data.count()),
+ isDonut: Boolean = true,
+ valueTextColor: Color = Color.Black,
+ onSectionClicked: (Float, Float) -> Unit = { _, _ -> }
+) {
+
+ if (data.isEmpty()) return
+
+ val total = data.sum()
+ val proportions = data.map {
+ it.times(100).div(total)
+ }
+ val angleProgress = proportions.map {
+ 360.times(it).div(100)
+ }
+
+ val currentProgressSize = mutableListOf().apply {
+ add(angleProgress.first())
+ }.also {
+ (1.until(angleProgress.size)).forEach { x ->
+ it.add(angleProgress[x].plus(it[x.minus(1)]))
+ }
+ }
+
+ val currentPie = remember { mutableStateOf(-1) }
+ var startAngle = 270f
+
+ BoxWithConstraints(modifier = modifier) {
+
+ val sideSize = min(constraints.maxWidth, constraints.maxHeight)
+ val padding = (sideSize.times(if (isDonut) 30 else 20)).div(100f)
+
+ val pathPortion = remember { Animatable(initialValue = 0f) }
+ LaunchedEffect(true) { pathPortion.animateTo(1f, animationSpec = tween(1000)) }
+
+ val size = Size(sideSize.toFloat().minus(padding), sideSize.toFloat().minus(padding))
+
+ Canvas(
+ modifier = Modifier
+ .width(sideSize.dp)
+ .height(sideSize.dp)
+ .handlePiePortionClick(sideSize, currentProgressSize, currentPie) {
+ onSectionClicked(proportions[it], data[it])
+ }
+ ) {
+
+ angleProgress.forEachIndexed { index, arcProgress ->
+ drawPie(
+ color = config.colors[index],
+ startAngle = startAngle,
+ arcProgress = arcProgress.times(pathPortion.value),
+ size = size,
+ padding = padding,
+ isDonut = isDonut,
+ isActive = currentPie.value == index
+ )
+ startAngle += arcProgress
+ }
+
+ if (currentPie.value != -1 && isDonut) {
+ drawPieSection(proportions, currentPie.value, valueTextColor, sideSize)
+ }
+ }
+ }
+}
+
+fun DrawScope.drawPieSection(
+ proportions: List,
+ currentPieValue: Int,
+ percentColor: Color,
+ sideSize: Int
+) {
+ drawContext.canvas.nativeCanvas.apply {
+ val fontSize = size.width.div(20).toDp().toPx()
+
+ drawText(
+ "${proportions[currentPieValue].roundToInt()}%",
+ (sideSize.div(2)) + fontSize.div(4), (sideSize.div(2)) + fontSize.div(3),
+ Paint().apply {
+ color = percentColor.toArgb()
+ textSize = fontSize
+ textAlign = Paint.Align.CENTER
+ }
+ )
+ }
+}
+
+private fun Modifier.handlePiePortionClick(
+ sideSize: Int,
+ currentProgressSize: MutableList,
+ currentPie: MutableState,
+ onIndexSelected: (Int) -> Unit
+): Modifier = pointerInput(true) {
+ detectTapGestures { offset ->
+ val clickedAngle = convertTouchEventPointToAngle(
+ sideSize.toFloat(),
+ sideSize.toFloat(),
+ offset.x,
+ offset.y
+ )
+ currentProgressSize.forEachIndexed { index, item ->
+ if (clickedAngle <= item) {
+ if (currentPie.value != index) {
+ currentPie.value = index
+ }
+ onIndexSelected(currentPie.value)
+
+ return@detectTapGestures
+ }
+ }
+ }
+}
+
+private fun DrawScope.drawPie(
+ color: Color,
+ startAngle: Float,
+ arcProgress: Float,
+ size: Size,
+ padding: Float,
+ isDonut: Boolean = false,
+ isActive: Boolean = false
+): Path {
+
+ return Path().apply {
+ drawArc(
+ color = color,
+ startAngle = startAngle,
+ sweepAngle = arcProgress,
+ useCenter = !isDonut,
+ size = size,
+ style = if (isDonut)
+ Stroke(
+ width = if (isActive) size.width.div(1.5F) else size.width.div(2),
+ ) else Fill,
+
+ topLeft = Offset(padding / 2, padding / 2)
+ )
+ }
+}
+
+private fun convertTouchEventPointToAngle(
+ width: Float,
+ height: Float,
+ xPos: Float,
+ yPos: Float
+): Double {
+ val x = xPos - (width * 0.5f)
+ val y = yPos - (height * 0.5f)
+
+ var angle = Math.toDegrees(kotlin.math.atan2(y.toDouble(), x.toDouble()) + Math.PI / 2)
+ angle = if (angle < 0) angle + 360 else angle
+ return angle
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/pie/config/PieConfig.kt b/charty/src/main/java/com/himanshoe/charty/pie/config/PieConfig.kt
new file mode 100644
index 0000000..3454066
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/pie/config/PieConfig.kt
@@ -0,0 +1,24 @@
+package com.himanshoe.charty.pie.config
+
+import androidx.compose.ui.graphics.Color
+import kotlin.random.Random
+
+data class PieConfig(
+ val colors: List,
+)
+
+internal object PieConfigDefaults {
+
+ fun pieConfigDefaults(count: Int) = PieConfig(
+ colors = generateRandomColor(count),
+ )
+}
+
+private fun generateRandomColor(count: Int): List {
+ val colors = mutableListOf()
+ repeat(count) {
+ val color = Color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))
+ colors.add(color)
+ }
+ return colors.toList()
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/point/PointChart.kt b/charty/src/main/java/com/himanshoe/charty/point/PointChart.kt
new file mode 100644
index 0000000..f9af12c
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/point/PointChart.kt
@@ -0,0 +1,126 @@
+package com.himanshoe.charty.point
+
+import android.graphics.Paint
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import com.himanshoe.charty.common.axis.AxisConfig
+import com.himanshoe.charty.common.axis.AxisConfigDefaults
+import com.himanshoe.charty.common.axis.xAxis
+import com.himanshoe.charty.common.axis.yAxis
+import com.himanshoe.charty.common.dimens.ChartDimens
+import com.himanshoe.charty.common.dimens.ChartDimensDefaults
+import com.himanshoe.charty.point.cofig.PointConfig
+import com.himanshoe.charty.point.cofig.PointConfigDefaults
+import com.himanshoe.charty.point.cofig.PointType
+import com.himanshoe.charty.point.model.PointData
+import com.himanshoe.charty.point.model.maxYValue
+
+@Composable
+fun PointChart(
+ pointData: List,
+ colors: List,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ pointConfig: PointConfig = PointConfigDefaults.pointConfigDefaults()
+) {
+ val maxYValueState = rememberSaveable { mutableStateOf(pointData.maxYValue()) }
+ val maxYValue = maxYValueState.value
+ val pointBound = remember { mutableStateOf(0F) }
+
+ Canvas(
+ modifier = modifier
+ .drawBehind {
+ if (axisConfig.showAxes) {
+ xAxis(axisConfig, maxYValue)
+ yAxis(axisConfig)
+ }
+ }
+ .padding(horizontal = chartDimens.horizontalPadding)
+
+ ) {
+ pointBound.value = size.width.div(pointData.count().times(1.2F))
+ val yChunck = size.height.div(maxYValue)
+ val brush = Brush.linearGradient(colors)
+ val radius = size.width.div(70)
+ pointData.forEachIndexed { index, data ->
+ val centerOffset = dataToOffSet(index, pointBound.value, size, data, yChunck)
+ val style = when (pointConfig.pointType) {
+ is PointType.Stroke -> Stroke(width = size.width.div(100))
+ else -> Fill
+ }
+
+ drawCircle(
+ center = centerOffset,
+ style = style,
+ radius = radius,
+ brush = brush
+ )
+ // draw label
+ drawXLabel(data, centerOffset, radius)
+ }
+ }
+}
+
+private fun DrawScope.drawXLabel(data: PointData, centerOffset: Offset, radius: Float) {
+ drawIntoCanvas {
+ it.nativeCanvas.apply {
+ drawText(
+ data.xValue.toString(),
+ centerOffset.x,
+ size.height.plus(radius.times(4)),
+ Paint().apply {
+ textSize = size.width.div(30)
+ textAlign = Paint.Align.CENTER
+ }
+ )
+ }
+ }
+}
+
+@Composable
+fun PointChart(
+ pointData: List,
+ color: Color,
+ modifier: Modifier = Modifier,
+ chartDimens: ChartDimens = ChartDimensDefaults.chartDimesDefaults(),
+ axisConfig: AxisConfig = AxisConfigDefaults.axisConfigDefaults(),
+ pointConfig: PointConfig = PointConfigDefaults.pointConfigDefaults()
+) {
+ PointChart(
+ pointData = pointData,
+ colors = listOf(color, color),
+ modifier = modifier,
+ chartDimens = chartDimens,
+ axisConfig = axisConfig,
+ pointConfig = pointConfig
+ )
+}
+
+private fun dataToOffSet(
+ index: Int,
+ bound: Float,
+ size: Size,
+ data: PointData,
+ yChunck: Float
+): Offset {
+ val startX = index.times(bound.times(1.2F))
+ val endX = index.plus(1).times(bound.times(1.2F))
+ val y = size.height.minus(data.yValue.times(yChunck))
+ return Offset(((startX.plus(endX)).div(2F)), y)
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/point/cofig/PointConfig.kt b/charty/src/main/java/com/himanshoe/charty/point/cofig/PointConfig.kt
new file mode 100644
index 0000000..a1b4fc7
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/point/cofig/PointConfig.kt
@@ -0,0 +1,17 @@
+package com.himanshoe.charty.point.cofig
+
+data class PointConfig(
+ val pointType: PointType,
+)
+
+internal object PointConfigDefaults {
+
+ fun pointConfigDefaults() = PointConfig(
+ pointType = PointType.Stroke
+ )
+}
+
+sealed interface PointType {
+ object Fill : PointType
+ object Stroke : PointType
+}
diff --git a/charty/src/main/java/com/himanshoe/charty/point/model/PointData.kt b/charty/src/main/java/com/himanshoe/charty/point/model/PointData.kt
new file mode 100644
index 0000000..36b297d
--- /dev/null
+++ b/charty/src/main/java/com/himanshoe/charty/point/model/PointData.kt
@@ -0,0 +1,7 @@
+package com.himanshoe.charty.point.model
+
+data class PointData(val xValue: Any, val yValue: Float)
+
+fun List.maxYValue() = maxOf {
+ it.yValue
+}
diff --git a/charty/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt b/charty/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt
new file mode 100644
index 0000000..77df319
--- /dev/null
+++ b/charty/src/test/java/com/himanshoe/charty/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.himanshoe.charty
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/docs/BarChart.md b/docs/BarChart.md
new file mode 100644
index 0000000..5caa4a5
--- /dev/null
+++ b/docs/BarChart.md
@@ -0,0 +1,40 @@
+## BarChart
+
+> A bar chart or bar graph is a chart or graph that presents categorical data with rectangular bars with heights or lengths proportional to the values that they represent
+
+### Using BarChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ BarChart(
+ modifier = Modifier,
+ onBarClick = { // handle Click for individual bar}
+ colors = // colors
+ barData = // list of BarData
+ )
+```
+
+2. When you want a solid shade:
+
+```kotlin
+ BarChart(
+ modifier = Modifier,
+ onBarClick = { // handle Click for individual bar},
+ color = // colors
+ barData = // list of BarData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `BarData`, where `BarData` looks like:
+```kotlin
+data class BarData(val xValue: Any, val yValue: Float)
+```
+Here, `xValue` will be used as Labels and `yValue` will represent the bars.
+
+### Additional Configuration (Optional)
+- To add padding the the chart, you can also use `ChartDimens`
+- To edit Config of the Axis, to suit your need to use `AxisConfig`
+- To edit Individual Bar config of it having corner radius you need to use `BarConfig`
diff --git a/docs/CircleChart.md b/docs/CircleChart.md
new file mode 100644
index 0000000..f441a68
--- /dev/null
+++ b/docs/CircleChart.md
@@ -0,0 +1,38 @@
+## CircleChart
+
+> A circle chart or bar graph is a chart or graph that presents categorical data with circular progress
+
+### Using CircleChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ CircleChart(
+ modifier = Modifier,
+ isAnimated = true/false,
+ colors = // colors,
+ circleData = // list of CircleData
+ )
+```
+
+2. When you want a solid shade:
+```kotlin
+ CircleChart(
+ modifier = Modifier,
+ isAnimated = true/false,
+ color = // color
+ circleData = // list of CircleData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `CircleData`, where `CircleData` looks like:
+```kotlin
+data class CircleData(val xValue: Any, val yValue: Float, val color: Color? = null)
+```
+Here, `xValue` will be used as label, `yValue` will act as progress and `color` is optional parameter but if provided that will represent the individual color of the progress
+
+### Additional Configuration (Optional)
+- To change the start position of the Circle, you can use `StartPosition` with either of provided values or your custom angle.
+- You can also provide a `maxValue` to wrap the max progress of the chart.
diff --git a/docs/CurveLineChart.md b/docs/CurveLineChart.md
new file mode 100644
index 0000000..83573ae
--- /dev/null
+++ b/docs/CurveLineChart.md
@@ -0,0 +1,38 @@
+## CurveLineChart
+
+> A curve line chart chart or graph that presents categorical data with line progress views with curved lines with heights proportional to the values that they represent
+
+### Using CurveLineChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ CurveLineChart(
+ modifier = Modifier,
+ colors = // colors
+ lineData = // list of LineData
+ )
+```
+
+2. When you want a solid shade:
+
+```kotlin
+ CurveLineChart(
+ modifier = Modifier,
+ color = // colors
+ lineData = // list of LineData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `LineData`, where `LineData` looks like:
+```kotlin
+data class LineData(val xValue: Any, val yValue: Float)
+```
+Here, `xValue` will be used as Labels and `yValue` will represent the progress.
+
+### Additional Configuration (Optional)
+- To add padding the the chart, you can also use `ChartDimens`
+- To edit Config of the Axis, to suit your need to use `AxisConfig`
+- To edit the line representation like `smooth curve` or having additional view of `dot market` use `LineConfig`
diff --git a/docs/GroupedBarChart.md b/docs/GroupedBarChart.md
new file mode 100644
index 0000000..864689f
--- /dev/null
+++ b/docs/GroupedBarChart.md
@@ -0,0 +1,29 @@
+## GroupedBarChart
+
+> grouped bar charts are Bar charts in which multiple sets of data items are compared, with a single color used to denote a specific series across all sets.
+
+### Using GroupedBarChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ GroupedBarChart(
+ modifier = Modifier,
+ onBarClick = { // handle Click for individual bar},
+ colors = // colors
+ groupedBarData = // list of GroupedBarData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `GroupedBarData`, where `GroupedBarData` looks like:
+```kotlin
+data class GroupedBarData(val barData: List, val colors: List)
+```
+Here, `barData` will be used as Creating group of bar and `colors` will represent the colors of the individual bar.
+
+### Additional Configuration (Optional)
+- To add padding the the chart, you can also use `ChartDimens`
+- To edit Config of the Axis, to suit your need to use `AxisConfig`
+- To edit Individual Bar config of it having corner radius you need to use `BarConfig`
diff --git a/docs/LineChart.md b/docs/LineChart.md
new file mode 100644
index 0000000..482d237
--- /dev/null
+++ b/docs/LineChart.md
@@ -0,0 +1,38 @@
+## LineChart
+
+> A line chart chart or graph that presents categorical data with line progress views with heights proportional to the values that they represent
+
+### Using LineChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ LineChart(
+ modifier = Modifier,
+ colors = // colors
+ lineData = // list of LineData
+ )
+```
+
+2. When you want a solid shade:
+
+```kotlin
+ LineChart(
+ modifier = Modifier,
+ color = // colors
+ lineData = // list of LineData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `LineData`, where `LineData` looks like:
+```kotlin
+data class LineData(val xValue: Any, val yValue: Float)
+```
+Here, `xValue` will be used as Labels and `yValue` will represent the progress.
+
+### Additional Configuration (Optional)
+- To add padding the the chart, you can also use `ChartDimens`
+- To edit Config of the Axis, to suit your need to use `AxisConfig`
+- To edit the line representation like `smooth curve` or having additional view of `dot market` use `LineConfig`
diff --git a/docs/PieChart.md b/docs/PieChart.md
new file mode 100644
index 0000000..07e8cf1
--- /dev/null
+++ b/docs/PieChart.md
@@ -0,0 +1,29 @@
+## PieChart
+
+### Using PieChart in your project:
+> A PieChart is a graph in which a circle is divided into sectors that each represent a proportion of the whole.
+
+1. When you want a gradient shade
+
+```kotlin
+ PieChart(
+ modifier = Modifier,
+ isDonut = true/false,
+ onSectionClicked = {},
+ valueTextColor = //Color,
+ colors = // colors
+ data = // list of Floats ,
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `Float`, where it will draw the value in proportion.
+
+### Additional Configuration (Optional)
+- To add your color set use `PieConfig` to pass on your set of color, make sure you use colors of total size of data.
+- To make the chart looks a donut make `isDonut` as `true`
+
+
+### Mention:
+- Muñoz : For base work inspiration for PieChart library
diff --git a/docs/PointChart.md b/docs/PointChart.md
new file mode 100644
index 0000000..2464178
--- /dev/null
+++ b/docs/PointChart.md
@@ -0,0 +1,36 @@
+## PointChart
+
+### Using PointChart in your project:
+
+1. When you want a gradient shade
+
+```kotlin
+ PointChart(
+ modifier = Modifier,
+ colors = // colors
+ pointData = // list of PointData
+ )
+```
+
+2. When you want a solid shade:
+
+```kotlin
+ PointChart(
+ modifier = Modifier,
+ color = // colors
+ pointData = // list of PointData
+ )
+```
+
+### Creating Data Set:
+
+to create a data set you need to pass List of `PointData`, where `PointData` looks like:
+```kotlin
+data class PointData(val xValue: Any, val yValue: Float)
+```
+Here, `xValue` will be used as Labels and `yValue` will represent the progress.
+
+### Additional Configuration (Optional)
+- To add padding the the chart, you can also use `ChartDimens`
+- To edit Config of the Axis, to suit your need to use `AxisConfig`
+- To edit the point representation use `PointConfig`
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..3c7a8bd
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f36ac86
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Aug 09 18:18:18 CEST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/img/banner.png b/img/banner.png
new file mode 100644
index 0000000..37db192
Binary files /dev/null and b/img/banner.png differ
diff --git a/img/charty-banner.png b/img/charty-banner.png
new file mode 100644
index 0000000..b8aaa12
Binary files /dev/null and b/img/charty-banner.png differ
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..af98932
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "charty-library"
+include(":app")
+include(":charty")