Repository: bytebeats/compose-charts
Branch: master
Commit: 06a4fc707b1c
Files: 83
Total size: 151.8 KB
Directory structure:
gitextract_1pinupfr/
├── .gitignore
├── LICENSE
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── me/
│ │ └── bytebeats/
│ │ └── views/
│ │ └── charts/
│ │ └── app/
│ │ ├── MainActivity.kt
│ │ └── ui/
│ │ ├── ComposeCharts.kt
│ │ ├── Screen.kt
│ │ ├── ScreenRouter.kt
│ │ ├── screen/
│ │ │ ├── HomeScreen.kt
│ │ │ ├── bar/
│ │ │ │ ├── BarChartDataModel.kt
│ │ │ │ └── BarChartScreen.kt
│ │ │ ├── line/
│ │ │ │ ├── LineChartDataModel.kt
│ │ │ │ ├── LineChartScreen.kt
│ │ │ │ └── PointDrawerType.kt
│ │ │ └── pie/
│ │ │ ├── PieChartDataModel.kt
│ │ │ └── PieChartScreen.kt
│ │ └── theme/
│ │ ├── Color.kt
│ │ ├── Margin.kt
│ │ ├── Shape.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── res/
│ ├── drawable/
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ └── values/
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle.kts
├── charts/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── me/
│ └── bytebeats/
│ └── views/
│ └── charts/
│ ├── Animations.kt
│ ├── AxisLine.kt
│ ├── Colors.kt
│ ├── TypeAlias.kt
│ ├── bar/
│ │ ├── BarChart.kt
│ │ ├── BarChartData.kt
│ │ ├── BarCharts.kt
│ │ └── render/
│ │ ├── bar/
│ │ │ ├── IBarDrawer.kt
│ │ │ └── SimpleBarDrawer.kt
│ │ ├── label/
│ │ │ ├── ILabelDrawer.kt
│ │ │ └── SimpleLabelDrawer.kt
│ │ ├── xaxis/
│ │ │ ├── IXAxisDrawer.kt
│ │ │ └── SimpleXAxisDrawer.kt
│ │ └── yaxis/
│ │ ├── IYAxisDrawer.kt
│ │ └── SimpleYAxisDrawer.kt
│ ├── line/
│ │ ├── LineChart.kt
│ │ ├── LineChartData.kt
│ │ ├── LineCharts.kt
│ │ └── render/
│ │ ├── line/
│ │ │ ├── EmptyLineShader.kt
│ │ │ ├── GradientLineShader.kt
│ │ │ ├── ILineDrawer.kt
│ │ │ ├── ILineShader.kt
│ │ │ ├── SolidLineDrawer.kt
│ │ │ └── SolidLineShader.kt
│ │ ├── point/
│ │ │ ├── EmptyPointDrawer.kt
│ │ │ ├── FilledCircularPointDrawer.kt
│ │ │ ├── HollowCircularPointDrawer.kt
│ │ │ └── IPointDrawer.kt
│ │ ├── xaxis/
│ │ │ ├── IXAxisDrawer.kt
│ │ │ └── SimpleXAxisDrawer.kt
│ │ └── yaxis/
│ │ ├── IYAxisDrawer.kt
│ │ └── SimpleYAxisDrawer.kt
│ ├── pie/
│ │ ├── PieChart.kt
│ │ ├── PieChartData.kt
│ │ ├── PieCharts.kt
│ │ └── render/
│ │ ├── ISliceDrawer.kt
│ │ └── SimpleSliceDrawer.kt
│ └── util/
│ └── Floats.kt
├── config/
│ └── detekt/
│ └── detekt.yml
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/.idea/
out/
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Chen Pan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# compose-charts
[](https://github.com/bytebeats/compose-charts/commit/)
[](https://github.com/bytebeats/compose-charts/graphs/contributors/)
[](https://github.com/bytebeats/compose-charts/issues/)
[](https://github.com/bytebeats/compose-charts/)
[](https://github.com/bytebeats/compose-charts/network/)
[](https://github.com/bytebeats/compose-charts/stargazers/)
[](https://github.com/bytebeats/compose-charts/watchers/)
Simple Jetpack Compose Charts for multi-platform. Including Android, Web, Desktop.
Compose Multiplatform for Desktop: [compose-charts-desktop](https://github.com/bytebeats/compose-charts-desktop).
**LATEST_VERSION**: 0.2.1
**COMPOSE_VERSION**: 1.6.7
**KOTLIN_VERSION**: 1.9.24
## Graph Effects


## How to use?
1. add maven and dependency:
1.1. add specific maven url in your root `build.gradle.kts`
```
repositories {
...
maven {
url = uri("https://repo1.maven.org/maven2/")
}
}
```
1.2. add dependency in your module `build.gradle.kts`
```
dependencies {
implementation("androidx.compose.ui:ui:$compose_version")
// implementation(project(":charts"))
implementation("io.github.bytebeats:compose-charts:${LATEST_VERSION}")
}
```
2. show Pie Chart in Jetpack Compose:
```
@Composable
fun PieChartView() {
PieChart(
pieChartData = PieChartData(
slices = listOf(
PieChartData.Slice(
randomLength(),
randomColor()
),
PieChartData.Slice(randomLength(), randomColor()),
PieChartData.Slice(randomLength(), randomColor())
)
),
// Optional properties.
modifier = Modifier.fillMaxSize(),
animation = simpleChartAnimation(),
sliceDrawer = SimpleSliceDrawer()
)
}
```
3. show Line Chart in Jetpack Compose:
```
@Composable
fun LineChartView() {
LineChart(
lineChartData = LineChartData(
points = listOf(
Point(randomYValue(), "Line 1"),
Point(randomYValue(), "Line 2"),
Point(randomYValue(), "Line 3"),
Point(randomYValue(), "Line 4"),
Point(randomYValue(), "Line 5"),
Point(randomYValue(), "Line 6"),
Point(randomYValue(), "Line 7")
)
),
// Optional properties.
modifier = Modifier.fillMaxSize(),
animation = simpleChartAnimation(),
pointDrawer = FilledCircularPointDrawer(),
lineDrawer = SolidLineDrawer(),
xAxisDrawer = SimpleXAxisDrawer(),
yAxisDrawer = SimpleYAxisDrawer(),
horizontalOffset = 5f
)
}
```
4. show Bar Chart in Jetpack Compose:
```
@Composable
fun BarChartView() {
BarChart(
barChartData = BarChartData(
bars = listOf(
BarChartData.Bar(
label = "Bar 1",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 2",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 3",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 4",
value = randomValue(),
color = randomColor()
),
)
),
// Optional properties.
modifier = Modifier.fillMaxSize(),
animation = simpleChartAnimation(),
barDrawer = SimpleBarDrawer(),
xAxisDrawer = SimpleXAxisDrawer(),
yAxisDrawer = SimpleYAxisDrawer(),
labelDrawer = SimpleLabelDrawer()
)
}
```
## Stargazers over time
[](https://starchart.cc/bytebeats/compose-charts)
## Github Stars Sparklines
[](https://stars.medv.io/bytebeats/compose-charts)
## Contributors
[](https://www.apiseven.com/en/contributor-graph?chart=contributorOverTime&repo=bytebeats/compose-charts)
## MIT License
Copyright (c) 2021 Chen Pan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: app/.gitignore
================================================
/build
.idea/
out/
================================================
FILE: app/build.gradle.kts
================================================
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.detekt.gradle.plugin)
}
android {
namespace = "me.bytebeats.views.charts.app"
compileSdk = 34
defaultConfig {
applicationId = "me.bytebeats.views.charts.app"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.ktCompilerExt.get()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
// implementation(project(":charts"))
implementation(libs.compose.charts)
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# 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
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/MainActivity.kt
================================================
package me.bytebeats.views.charts.app
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import me.bytebeats.views.charts.app.ui.ComposeCharts
/**
* Main activity
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCharts()
}
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/ComposeCharts.kt
================================================
package me.bytebeats.views.charts.app.ui
import androidx.compose.animation.Crossfade
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import me.bytebeats.views.charts.app.ui.screen.HomeScreen
import me.bytebeats.views.charts.app.ui.screen.bar.BarChartScreen
import me.bytebeats.views.charts.app.ui.screen.line.LineChartScreen
import me.bytebeats.views.charts.app.ui.screen.pie.PieChartScreen
import me.bytebeats.views.charts.app.ui.theme.ComposeChartsTheme
/**
* Created by bytebeats on 2021/9/30 : 16:24
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@Composable
fun ComposeCharts() {
ComposeChartsTheme {
ComposeChartsContent()
}
}
@Composable
private fun ComposeChartsContent() {
Crossfade(
targetState = ScreenRouter.currentScreen,
label = "Compose Charts"
) { screen ->
Surface(
color = MaterialTheme.colorScheme.background
) {
when (screen) {
Screen.Pie -> PieChartScreen()
Screen.Line -> LineChartScreen()
Screen.Bar -> BarChartScreen()
else -> HomeScreen()
}
}
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/Screen.kt
================================================
package me.bytebeats.views.charts.app.ui
/**
* Created by bytebeats on 2021/9/30 : 11:34
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
enum class Screen {
Home,
Pie,
Bar,
Line;
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/ScreenRouter.kt
================================================
package me.bytebeats.views.charts.app.ui
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
/**
* Created by bytebeats on 2021/9/30 : 11:39
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
object ScreenRouter {
var currentScreen by mutableStateOf(Screen.Home)
/**
* Navigate
*
* @param screen
*/
fun navigate(screen: Screen) {
currentScreen = screen
}
/**
* Navigate home
*/
fun navigateHome() {
currentScreen = Screen.Home
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/HomeScreen.kt
================================================
package me.bytebeats.views.charts.app.ui.screen
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import me.bytebeats.views.charts.app.ui.Screen
import me.bytebeats.views.charts.app.ui.ScreenRouter
import me.bytebeats.views.charts.app.ui.theme.Margin
/**
* Created by bytebeats on 2021/9/30 : 11:43
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Compose Charts")
}
)
}
) { paddingValues ->
HomeScreenContent(Modifier.padding(paddingValues))
}
}
@Composable
private fun HomeScreenContent(modifier: Modifier) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
ChartScreenSelector(
text = "Pie Chart",
nextScreen = Screen.Pie
)
ChartScreenSelector(
text = "Line Chart",
nextScreen = Screen.Line
)
ChartScreenSelector(
text = "Bar Chart",
nextScreen = Screen.Bar
)
}
}
@Composable
private fun ChartScreenSelector(
text: String,
nextScreen: Screen
) {
Row(
modifier = Modifier.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
) {
TextButton(
onClick = { ScreenRouter.navigate(nextScreen) }
) {
Text(text = text)
}
}
}
@Preview
@Composable
private fun HomeScreenPreview() = HomeScreen()
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/bar/BarChartDataModel.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.bar
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import me.bytebeats.views.charts.bar.BarChartData
import me.bytebeats.views.charts.bar.render.label.SimpleLabelDrawer
import kotlin.random.Random
/**
* Created by bytebeats on 2021/9/30 : 19:39
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
class BarChartDataModel {
private var colors = mutableListOf(
Color(0XFFF44336),
Color(0XFFE91E63),
Color(0XFF9C27B0),
Color(0XFF673AB7),
Color(0XFF3F51B5),
Color(0XFF03A9F4),
Color(0XFF009688),
Color(0XFFCDDC39),
Color(0XFFFFC107),
Color(0XFFFF5722),
Color(0XFF795548),
Color(0XFF9E9E9E),
Color(0XFF607D8B)
)
var labelDrawer by mutableStateOf(SimpleLabelDrawer(drawLocation = SimpleLabelDrawer.DrawLocation.Inside))
private set
var barChartData by mutableStateOf(
BarChartData(
bars = listOf(
BarChartData.Bar(
label = "Bar 1",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 2",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 3",
value = randomValue(),
color = randomColor()
),
BarChartData.Bar(
label = "Bar 4",
value = randomValue(),
color = randomColor()
),
)
)
)
val bars: List
get() = barChartData.bars
var labelLocation: SimpleLabelDrawer.DrawLocation = SimpleLabelDrawer.DrawLocation.Inside
set(value) {
val color = when (value) {
SimpleLabelDrawer.DrawLocation.Inside -> Color.White
SimpleLabelDrawer.DrawLocation.Outside, SimpleLabelDrawer.DrawLocation.XAxis -> Color.Black
}
labelDrawer = SimpleLabelDrawer(drawLocation = value, labelTextColor = color)
field = value
}
internal fun addBar() {
barChartData = barChartData.copy(bars = bars.toMutableList().apply {
add(
BarChartData.Bar(
label = "Bar ${bars.size + 1}",
value = randomValue(),
color = randomColor()
)
)
}.toList())
}
internal fun removeBar() {
barChartData = barChartData.copy(bars = bars.toMutableList().apply {
val lastBar = bars.last()
colors.add(lastBar.color)
remove(lastBar)
})
}
private fun randomValue(): Float = Random.Default.nextInt(25, 125).toFloat()
private fun randomColor(): Color {
val idx = Random.Default.nextInt(colors.size)
return colors.removeAt(idx)
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/bar/BarChartScreen.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.bar
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.app.ui.ScreenRouter
import me.bytebeats.views.charts.app.ui.theme.Margin
import me.bytebeats.views.charts.bar.BarChart
import me.bytebeats.views.charts.bar.render.label.SimpleLabelDrawer.DrawLocation
import me.bytebeats.views.charts.bar.render.yaxis.SimpleYAxisDrawer
/**
* Created by bytebeats on 2021/9/30 : 19:53
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BarChartScreen() {
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(
onClick = { ScreenRouter.navigateHome() }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back home"
)
}
},
title = { Text(text = "Bar Chart") })
}) { paddingValues ->
BarChartContent(Modifier.padding(paddingValues))
}
}
@Composable
private fun BarChartContent(modifier: Modifier = Modifier) {
val barChartDataModel = BarChartDataModel()
Column(
modifier = modifier.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
) {
BarChartRow(barChartDataModel = barChartDataModel)
DrawLabelLocation(
barChartDataModel = barChartDataModel,
newLocation = { barChartDataModel.labelLocation = it }
)
AddOrRemoveBar(barChartDataModel = barChartDataModel)
}
}
@Composable
private fun BarChartRow(barChartDataModel: BarChartDataModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(280.dp)
.padding(vertical = Margin.verticalLarge)
) {
BarChart(
barChartData = barChartDataModel.barChartData,
labelDrawer = barChartDataModel.labelDrawer,
yAxisDrawer = SimpleYAxisDrawer(labelValueFormatter = { value ->
"your regex here".format(
value
)
}
)
)
}
}
@Composable
private fun DrawLabelLocation(
barChartDataModel: BarChartDataModel,
newLocation: (DrawLocation) -> Unit
) {
val labelDrawLocation = remember(barChartDataModel.labelDrawer) {
barChartDataModel.labelLocation
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = Margin.verticalLarge),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
.align(Alignment.CenterVertically),
horizontalArrangement = Arrangement.SpaceEvenly
) {
for (location in DrawLocation.entries) {
OutlinedButton(
onClick = { newLocation(location) },
border = ButtonDefaults.outlinedButtonBorder.takeIf { labelDrawLocation == location },
) {
Text(text = location.name)
}
}
}
}
}
@Composable
private fun AddOrRemoveBar(barChartDataModel: BarChartDataModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = Margin.vertical),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Button(
onClick = { barChartDataModel.removeBar() },
enabled = barChartDataModel.bars.size > 1,
shape = CircleShape
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Remove bar from BarChart"
)
}
Row(
modifier = Modifier.padding(horizontal = Margin.horizontal),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Bars: ")
Text(
text = barChartDataModel.bars.size.toString(),
style = TextStyle(
fontWeight = FontWeight.ExtraBold,
fontSize = 18.sp
)
)
}
Button(
onClick = { barChartDataModel.addBar() },
enabled = barChartDataModel.bars.size < 7,
shape = CircleShape
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Add bar into BarChart"
)
}
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/line/LineChartDataModel.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.line
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import me.bytebeats.views.charts.line.LineChartData
import me.bytebeats.views.charts.line.LineChartData.Point
import me.bytebeats.views.charts.line.render.point.EmptyPointDrawer
import me.bytebeats.views.charts.line.render.point.FilledCircularPointDrawer
import me.bytebeats.views.charts.line.render.point.HollowCircularPointDrawer
import me.bytebeats.views.charts.line.render.point.IPointDrawer
import kotlin.random.Random
/**
* Created by bytebeats on 2021/9/30 : 17:49
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
class LineChartDataModel {
var lineChartData by mutableStateOf(
LineChartData(
points = listOf(
Point(randomYValue(), "Label 1"),
Point(randomYValue(), "Label 2"),
Point(randomYValue(), "Label 3"),
Point(randomYValue(), "Label 4"),
Point(randomYValue(), "Label 5"),
Point(randomYValue(), "Label 6"),
Point(randomYValue(), "Label 7")
)
)
)
var horizontalOffset by mutableFloatStateOf(5F)
var pointDrawerType by mutableStateOf(PointDrawerType.Hollow)
val pointDrawer: IPointDrawer
get() {
return when (pointDrawerType) {
PointDrawerType.None -> EmptyPointDrawer
PointDrawerType.Filled -> FilledCircularPointDrawer()
PointDrawerType.Hollow -> HollowCircularPointDrawer()
}
}
private fun randomYValue(): Float = Random.Default.nextInt(45, 145).toFloat()
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/line/LineChartScreen.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.line
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import me.bytebeats.views.charts.app.ui.ScreenRouter
import me.bytebeats.views.charts.app.ui.theme.Margin
import me.bytebeats.views.charts.line.LineChart
/**
* Created by bytebeats on 2021/9/30 : 17:55
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LineChartScreen() {
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(
onClick = { ScreenRouter.navigateHome() }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back home"
)
}
},
title = { Text(text = "Line Chart") }
)
}
) { paddingValues ->
LineChartContent(Modifier.padding(paddingValues))
}
}
@Composable
private fun LineChartContent(
modifier: Modifier = Modifier
) {
val lineChartData = LineChartDataModel()
Column(
modifier = modifier.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
) {
LineChartRow(lineChartDataModel = lineChartData)
HorizontalOffsetSelector(lineChartDataModel = lineChartData)
OffsetProgress(lineChartDataModel = lineChartData)
}
}
@Composable
private fun LineChartRow(lineChartDataModel: LineChartDataModel) {
Box(
modifier = Modifier
.height(250.dp)
.fillMaxSize()
) {
LineChart(
lineChartData = lineChartDataModel.lineChartData,
horizontalOffset = lineChartDataModel.horizontalOffset,
pointDrawer = lineChartDataModel.pointDrawer
)
}
}
@Composable
private fun HorizontalOffsetSelector(lineChartDataModel: LineChartDataModel) {
val pointDrawType = lineChartDataModel.pointDrawerType
Column(
modifier = Modifier.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Point Drawer")
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
),
horizontalArrangement = Arrangement.SpaceEvenly
) {
for (drawerType in PointDrawerType.entries) {
OutlinedButton(
onClick = { lineChartDataModel.pointDrawerType = drawerType },
border = ButtonDefaults.outlinedButtonBorder.takeIf { pointDrawType == drawerType },
) {
Text(text = drawerType.name)
}
}
}
}
}
@Composable
private fun OffsetProgress(lineChartDataModel: LineChartDataModel) {
Column(
modifier = Modifier.padding(horizontal = Margin.horizontal),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Offset")
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
.align(Alignment.CenterHorizontally)
) {
Slider(
value = lineChartDataModel.horizontalOffset,
onValueChange = { lineChartDataModel.horizontalOffset = it },
valueRange = 0F.rangeTo(25F)
)
}
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/line/PointDrawerType.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.line
/**
* Created by bytebeats on 2021/9/30 : 17:48
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
enum class PointDrawerType {
None,
Filled,
Hollow;
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/pie/PieChartDataModel.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.pie
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import me.bytebeats.views.charts.pie.PieChartData
import kotlin.random.Random
/**
* Created by bytebeats on 2021/9/30 : 12:03
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
class PieChartDataModel {
private val colors = mutableListOf(
Color(0XFFF44336),
Color(0XFFE91E63),
Color(0XFF9C27B0),
Color(0XFF673AB7),
Color(0XFF3F51B5),
Color(0XFF03A9F4),
Color(0XFF009688),
Color(0XFFCDDC39),
Color(0XFFFFC107),
Color(0XFFFF5722),
Color(0XFF795548),
Color(0XFF9E9E9E),
Color(0XFF607D8B)
)
var sliceThickness by mutableFloatStateOf(25F)
var pieChartData by mutableStateOf(
PieChartData(
slices = listOf(
PieChartData.Slice(
value = randomLength(),
color = randomColor()
),
PieChartData.Slice(
value = randomLength(),
color = randomColor()
),
PieChartData.Slice(
value = randomLength(),
color = randomColor()
)
)
)
)
val slices
get() = pieChartData.slices
internal fun addSlice() {
pieChartData = pieChartData.copy(
slices = slices.toMutableList().apply {
add(
PieChartData.Slice(
value = randomLength(),
color = randomColor()
)
)
}.toList()
)
}
internal fun removeSlice() {
pieChartData = pieChartData.copy(
slices = slices.toMutableList().apply {
val lastSlice = slices.last()
colors.add(lastSlice.color)
remove(lastSlice)
}.toList()
)
}
private fun randomLength(): Float = Random.Default.nextInt(10, 30).toFloat()
private fun randomColor(): Color {
val randomIndex = Random.Default.nextInt(colors.size)
return colors.removeAt(randomIndex)
}
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/screen/pie/PieChartScreen.kt
================================================
package me.bytebeats.views.charts.app.ui.screen.pie
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.app.ui.ScreenRouter
import me.bytebeats.views.charts.app.ui.theme.Margin
import me.bytebeats.views.charts.pie.PieChart
import me.bytebeats.views.charts.pie.render.SimpleSliceDrawer
/**
* Created by bytebeats on 2021/9/30 : 15:50
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PieChartScreen() {
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(
onClick = { ScreenRouter.navigateHome() }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back Home"
)
}
},
title = { Text(text = "Pie Chart") })
}
) { paddingValues ->
PieChartScreenContent(Modifier.padding(paddingValues))
}
}
@Composable
private fun PieChartScreenContent(
modifier: Modifier = Modifier
) {
val pieChartDataModel = remember {
PieChartDataModel()
}
Column(
modifier = modifier.padding(
horizontal = Margin.horizontal,
vertical = Margin.vertical
)
) {
PieChartRow(pieChartDataModel = pieChartDataModel)
SliceThicknessRow(
sliceThickness = pieChartDataModel.sliceThickness,
onValueUpdated = { pieChartDataModel.sliceThickness = it },
)
AddOrRemoveSliceRow(pieChartDataModel = pieChartDataModel)
}
}
@Composable
private fun PieChartRow(pieChartDataModel: PieChartDataModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.padding(vertical = Margin.vertical)
) {
PieChart(
pieChartData = pieChartDataModel.pieChartData,
sliceDrawer = SimpleSliceDrawer(sliceThickness = pieChartDataModel.sliceThickness)
)
}
}
@Composable
private fun SliceThicknessRow(sliceThickness: Float, onValueUpdated: (Float) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = Margin.verticalLarge),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Slice Thickness: ",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = Margin.horizontal)
)
Slider(
value = sliceThickness,
onValueChange = onValueUpdated,
valueRange = 10F.rangeTo(100F)
)
}
}
@Composable
private fun AddOrRemoveSliceRow(pieChartDataModel: PieChartDataModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = Margin.vertical),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Button(
onClick = { pieChartDataModel.removeSlice() },
enabled = pieChartDataModel.slices.size > 3,
shape = CircleShape
) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = "Remove slice from PieChart"
)
}
Row(
modifier = Modifier.padding(horizontal = Margin.horizontal),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Slices: ")
Text(
text = pieChartDataModel.slices.count().toString(),
style = TextStyle(
fontWeight = FontWeight.ExtraBold,
fontSize = 18.sp
)
)
}
Button(
onClick = { pieChartDataModel.addSlice() },
enabled = pieChartDataModel.slices.size < 9,
shape = CircleShape
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Add Slice to PieChart"
)
}
}
}
@Preview
@Composable
private fun PieChartPreview() = PieChartScreen()
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/theme/Color.kt
================================================
package me.bytebeats.views.charts.app.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/theme/Margin.kt
================================================
package me.bytebeats.views.charts.app.ui.theme
import androidx.compose.ui.unit.dp
/**
* Created by bytebeats on 2021/9/30 : 11:52
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
internal object Margin {
val horizontal = 15.dp
val horizontalLarge = 30.dp
val vertical = 15.dp
val verticalLarge = 30.dp
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/theme/Shape.kt
================================================
package me.bytebeats.views.charts.app.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/theme/Theme.kt
================================================
package me.bytebeats.views.charts.app.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColorScheme(
primary = Purple200,
primaryContainer = Purple700,
secondary = Teal200
)
private val LightColorPalette = lightColorScheme(
primary = Purple500,
primaryContainer = 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 ComposeChartsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colorScheme = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
================================================
FILE: app/src/main/java/me/bytebeats/views/charts/app/ui/theme/Type.kt
================================================
package me.bytebeats.views.charts.app.ui.theme
import androidx.compose.material3.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(
bodyLarge = 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
)
*/
)
================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#FFBB86FC
#FF6200EE
#FF3700B3
#FF03DAC5
#FF018786
#FF000000
#FFFFFFFF
================================================
FILE: app/src/main/res/values/strings.xml
================================================
compose-charts
================================================
FILE: app/src/main/res/values/themes.xml
================================================
================================================
FILE: build.gradle.kts
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.android.library) apply false
}
================================================
FILE: charts/.gitignore
================================================
/build/
.idea/
out/
================================================
FILE: charts/build.gradle.kts
================================================
import com.android.build.gradle.LibraryExtension
import org.gradle.jvm.tasks.Jar
import org.jetbrains.dokka.gradle.DokkaTask
import java.net.URI
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.dokka)
alias(libs.plugins.detekt.gradle.plugin)
id("maven-publish")
id("signing")
}
group = getProperty("GROUP_ID")
version = getProperty("COMPOSE_CHARTS_VERSION")
android {
namespace = "me.bytebeats.views.charts"
compileSdk = 34
defaultConfig {
minSdk = 24
lint {
targetSdk = 34
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.ktCompilerExt.get()
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
debugImplementation(libs.androidx.ui.tooling)
}
val sourcesJar by tasks.registering(Jar::class) {
archiveClassifier.set("sources")
if (project.plugins.hasPlugin(libs.plugins.android.library.get().pluginId)) {
val libExt = checkNotNull(project.extensions.findByType(LibraryExtension::class.java))
val libMainSourceSet = libExt.sourceSets.getByName("main")
from(libMainSourceSet.java.srcDirs)
} else {
val sourceSetExt =
checkNotNull(project.extensions.findByType(SourceSetContainer::class.java))
val mainSourceSet = sourceSetExt.getByName("main")
from(mainSourceSet.java.srcDirs)
}
}
tasks.withType(GenerateModuleMetadata::class).configureEach {
dependsOn(sourcesJar)
}
tasks.dokkaHtml {
outputDirectory.set(layout.buildDirectory.dir("dokka"))
moduleName.set(getProperty("MODULE_NAME"))
dokkaSourceSets {
configureEach {
suppress = false
offlineMode = false
includeNonPublic = false
skipDeprecated = true
skipEmptyPackages = true
noStdlibLink = true
noJdkLink = true
noAndroidSdkLink = false
jdkVersion = JavaVersion.VERSION_1_8.ordinal + 1
}
}
}
val dokkaHtml by tasks.getting(DokkaTask::class)
val javadocJar by tasks.registering(Jar::class) {
dependsOn(dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaHtml.outputDirectory)
}
fun Project.getProperty(key: String?, default: String? = null): String {
checkPropertyKey(key)
return properties[key]?.toString() ?: System.getProperty(key!!, default)
}
fun checkPropertyKey(key: String?) {
if (key == null) {
throw NullPointerException("key can't be null")
}
if (key.isBlank()) {
throw IllegalArgumentException("key can't be blank")
}
}
fun Project.checkSigningKey(signingKey: String?) {
checkPropertyKey(signingKey)
signingKey?.let { key ->
if (hasProperty(key).not() && System.getProperties().containsKey(key).not()) {
throw IllegalStateException("$signingKey has to be declared in local.properties or ~/.gradle/gradle.properties")
}
}
}
fun Project.getRepoUrl(): URI {
val isSnapshot = getProperty("COMPOSE_CHARTS_VERSION").contains("SNAPSHOT")
val releaseUrl = getProperty(
"RELEASES_REPO_URL",
"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
)
val snapshotUrl = getProperty(
"SNAPSHOTS_REPO_URL",
"https://s01.oss.sonatype.org/content/repositories/snapshots/"
)
return uri(if (isSnapshot) snapshotUrl else releaseUrl)
}
afterEvaluate {
publishing {
publications {
// 1. configure repositories
repositories {
maven {
name = project.getProperty("REPO_NAME")
url = project.getRepoUrl()
credentials {
username = project.getProperty("ossrhUsername", "")
password = project.getProperty("ossrhPassword", "")
}
}
}
// 2. configure publication
val publicationName = project.getProperty("PUBLICATION_NAME", "release")
create(publicationName) {
if (project.plugins.hasPlugin(libs.plugins.android.library.get().pluginId)) {
from(components["release"])
} else {
from(components["java"])
}
artifact(sourcesJar.get())
artifact(javadocJar.get())
pom {
groupId = project.getProperty("GROUP_ID")
artifactId = project.getProperty("COMPOSE_CHARTS_ARTIFACT_ID")
version = project.getProperty("COMPOSE_CHARTS_VERSION")
inceptionYear = project.getProperty("COMPOSE_CHARTS_INCEPTION_YEAR")
name = project.getProperty("MODULE_NAME")
description = project.getProperty("COMPOSE_CHARTS_DESCRIPTION")
url = project.getProperty("COMPOSE_CHARTS_URL")
packaging = project.getProperty("COMPOSE_CHARTS_PACKAGING")
scm {
url = project.getProperty("SCM_URL")
connection = project.getProperty("SCM_CONNECTION")
developerConnection = project.getProperty("SCM_DEVELOPER_CONNECTION")
}
organization {
name = project.getProperty("ORGANIZATION_NAME", "")
url = project.getProperty("ORGANIZATION_URL", "")
}
developers {
developer {
id = project.getProperty("DEVELOPER_ID")
name = project.getProperty("DEVELOPER_NAME")
url = project.getProperty("DEVELOPER_URL")
email = project.getProperty("DEVELOPER_EMAIL")
}
}
licenses {
license {
name = project.getProperty("LICENSE_NAME")
url = project.getProperty("LICENSE_URL")
distribution = project.getProperty("LICENCE_DIST")
}
}
issueManagement {
system = project.getProperty("ISSUE_SYSTEM")
url = project.getProperty("ISSUE_URL")
}
contributors {
contributor {
name = project.getProperty("CONTRIBUTOR_NAME")
email = project.getProperty("CONTRIBUTOR_EMAIL")
url = project.getProperty("CONTRIBUTOR_URL")
roles.set(listOf("Master", "Maintainer", "Developer"))
timezone = project.getProperty("CONTRIBUTOR_TIMEZONE")
}
}
ciManagement {
system = project.getProperty("CI_SYSTEM")
url = project.getProperty("CI_URL")
}
distributionManagement {
downloadUrl = getProperty("RELEASES_REPO_URL")
}
}
}
// 3. sign the artifacts
signing {
// Choose one of both ways to sign the aar
// Signing with gpg
// and with its signing.keyId & signing.password & signing.secretKeyRingFile
// declared in local.properties or ~/.gradle/gradle.properties
// checkSigningKey("signing.keyId")
// checkSigningKey("signing.password")
// checkSigningKey("signing.secretKeyRingFile")
sign(publishing.publications.getByName(publicationName))
// or signing with CI/CD
// and with its signingKeyId & signingKeyPassword & signingKey
// declared in local.properties or ~/.gradle/gradle.properties
// checkSigningKey("signingKeyId")
// checkSigningKey("signingKey")
// checkSigningKey("signingKeyPassword")
// val signingKeyId = getProperty("signingKeyId")
// val signingKey = getProperty("signingKey")
// val signingKeyPassword = getProperty("signingKeyPassword")
// useInMemoryPgpKeys(signingKeyId, signingKey, signingKeyPassword)
// sign(publishing.publications.getByName(publicationName))
}
}
}
}
================================================
FILE: charts/consumer-rules.pro
================================================
================================================
FILE: charts/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# 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
================================================
FILE: charts/src/main/AndroidManifest.xml
================================================
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/Animations.kt
================================================
package me.bytebeats.views.charts
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.TweenSpec
/**
* Created by bytebeats on 2021/9/24 : 10:53
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
fun simpleChartAnimation(): AnimationSpec = TweenSpec(durationMillis = 500)
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/AxisLine.kt
================================================
package me.bytebeats.views.charts
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Created by bytebeats on 2021/9/24 : 10:47
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class AxisLine(
val thickness: Dp = 1.5.dp,
val color: Color = Color.Gray
) {
private val mPaint by lazy {
Paint().apply {
color = this@AxisLine.color
style = PaintingStyle.Stroke
}
}
@Suppress("UndocumentedPublicFunction")
fun paint(density: Density) {
mPaint.strokeWidth = thickness.value * density.density
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/Colors.kt
================================================
package me.bytebeats.views.charts
import androidx.compose.ui.graphics.Color
import me.bytebeats.views.charts.util.FLOAT_0_5
import me.bytebeats.views.charts.util.FLOAT_255
/**
* Created by bytebeats on 2021/9/24 : 10:51
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
fun Color.toLegacyInt(): Int {
return android.graphics.Color.argb(
(alpha * FLOAT_255 + FLOAT_0_5).toInt(),
(red * FLOAT_255 + FLOAT_0_5).toInt(),
(green * FLOAT_255 + FLOAT_0_5).toInt(),
(blue * FLOAT_255 + FLOAT_0_5).toInt()
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/TypeAlias.kt
================================================
package me.bytebeats.views.charts
/**
* Created by bytebeats on 2021/9/25 : 15:37
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
typealias LabelFormatter = (value: Float) -> String
typealias AxisLabelFormatter = (value: Any?) -> String
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/BarChart.kt
================================================
package me.bytebeats.views.charts.bar
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import me.bytebeats.views.charts.bar.render.bar.IBarDrawer
import me.bytebeats.views.charts.bar.render.bar.SimpleBarDrawer
import me.bytebeats.views.charts.bar.render.label.ILabelDrawer
import me.bytebeats.views.charts.bar.render.label.SimpleLabelDrawer
import me.bytebeats.views.charts.bar.render.xaxis.IXAxisDrawer
import me.bytebeats.views.charts.bar.render.xaxis.SimpleXAxisDrawer
import me.bytebeats.views.charts.bar.render.yaxis.IYAxisDrawer
import me.bytebeats.views.charts.bar.render.yaxis.SimpleYAxisDrawer
import me.bytebeats.views.charts.simpleChartAnimation
/**
* Created by bytebeats on 2021/9/25 : 15:56
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@Composable
fun BarChart(
barChartData: BarChartData,
modifier: Modifier = Modifier,
animation: AnimationSpec = simpleChartAnimation(),
barDrawer: IBarDrawer = SimpleBarDrawer(),
xAxisDrawer: IXAxisDrawer = SimpleXAxisDrawer(),
yAxisDrawer: IYAxisDrawer = SimpleYAxisDrawer(),
labelDrawer: ILabelDrawer = SimpleLabelDrawer()
) {
val transitionAnimation = remember(barChartData.bars) {
Animatable(initialValue = 0F)
}
LaunchedEffect(barChartData.bars) {
transitionAnimation.animateTo(1F, animationSpec = animation)
}
val progress = transitionAnimation.value
Canvas(
modifier = modifier
.fillMaxSize()
.drawBehind {
drawIntoCanvas { canvas ->
val (xAxisArea, yAxisArea) = axisAreas(
drawScope = this,
totalSize = size,
xAxisDrawer = xAxisDrawer,
labelDrawer = labelDrawer
)
val barDrawableArea = barDrawableArea(xAxisArea)
yAxisDrawer.drawAxisLine(
drawScope = this,
canvas = canvas,
drawableArea = yAxisArea
)
xAxisDrawer.drawXAxisLine(
drawScope = this,
canvas = canvas,
drawableArea = xAxisArea
)
barChartData.forEachWithArea(
this,
barDrawableArea,
progress,
labelDrawer
) { barArea, bar ->
barDrawer.drawBar(drawScope = this, canvas, barArea, bar)
}
}
}
) {
drawIntoCanvas { canvas ->
val (xAxisArea, yAxisArea) = axisAreas(
drawScope = this,
totalSize = size,
xAxisDrawer = xAxisDrawer,
labelDrawer = labelDrawer
)
val barDrawableArea = barDrawableArea(xAxisArea)
barChartData.forEachWithArea(
this,
barDrawableArea,
progress,
labelDrawer
) { barArea, bar ->
labelDrawer.drawLabel(
drawScope = this,
canvas = canvas,
label = bar.label,
barArea = barArea,
xAxisArea = xAxisArea
)
}
yAxisDrawer.drawAxisLabels(
drawScope = this,
canvas = canvas,
minValue = barChartData.minY,
maxValue = barChartData.maxY,
drawableArea = yAxisArea
)
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/BarChartData.kt
================================================
package me.bytebeats.views.charts.bar
import androidx.compose.ui.graphics.Color
import me.bytebeats.views.charts.util.FLOAT_100
/**
* Created by bytebeats on 2021/9/25 : 13:52 E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class BarChartData(
val bars: List,
val padBy: Float = 10F,
val startAtZero: Boolean = true,
val maxBarValue: Float = bars.maxOf { it.value }
) {
init {
require(padBy in 0F..FLOAT_100) {
"padBy must be between 0F and 100F, included"
}
require(maxBarValue >= bars.maxOf { it.value }) {
"maxBarValue must be at least the value of the highest bar"
}
}
private val yMinMaxValues: Pair
get() {
val minValue = bars.minOf { it.value }
val maxValue = maxBarValue
return minValue to maxValue
}
val maxY: Float
get() = yMinMaxValues.second + (yMinMaxValues.second - yMinMaxValues.first) * padBy / FLOAT_100
val minY: Float
get() = if (startAtZero) 0F
else yMinMaxValues.first - (yMinMaxValues.second - yMinMaxValues.first) * padBy / FLOAT_100
data class Bar(
val value: Float,
val color: Color,
val label: String
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/BarCharts.kt
================================================
package me.bytebeats.views.charts.bar
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.dp
import me.bytebeats.views.charts.bar.render.label.ILabelDrawer
import me.bytebeats.views.charts.bar.render.xaxis.IXAxisDrawer
import me.bytebeats.views.charts.util.FLOAT_10
import me.bytebeats.views.charts.util.FLOAT_100
/**
* Created by bytebeats on 2021/9/25 : 13:57
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
internal fun axisAreas(
drawScope: DrawScope,
totalSize: Size,
xAxisDrawer: IXAxisDrawer,
labelDrawer: ILabelDrawer
): Pair {
with(drawScope) {
val yAxisTop = labelDrawer.requiredAboveBarHeight(drawScope)
val yAxisRight = 50.dp.toPx().coerceAtMost(size.width * FLOAT_10 / FLOAT_100)
val xAxisRight = totalSize.width
val xAxisTop = totalSize.height - xAxisDrawer.requiredHeight(drawScope)
return Rect(
left = yAxisRight,
top = xAxisTop,
right = xAxisRight,
bottom = totalSize.height
) to Rect(
left = 0F,
top = yAxisTop,
right = yAxisRight,
bottom = xAxisTop
)
}
}
internal fun barDrawableArea(xAxisArea: Rect): Rect =
Rect(
left = xAxisArea.left,
top = 0F,
right = xAxisArea.right,
bottom = xAxisArea.top
)
internal fun BarChartData.forEachWithArea(
drawScope: DrawScope,
barDrawableArea: Rect,
progress: Float,
labelDrawer: ILabelDrawer,
block: (barArea: Rect, bar: BarChartData.Bar) -> Unit
) {
val barCount = bars.size
val widthOfBarArea = barDrawableArea.width / barCount
val offsetOfBar = widthOfBarArea * 0.2F
bars.forEachIndexed { index, bar ->
val left = barDrawableArea.left + index * widthOfBarArea
val height = barDrawableArea.height
val barHeight = (height - labelDrawer.requiredAboveBarHeight(drawScope)) * progress
val barArea = Rect(
left = left + offsetOfBar,
top = barDrawableArea.bottom - bar.value / maxBarValue * barHeight,
right = left + widthOfBarArea - offsetOfBar,
bottom = barDrawableArea.bottom
)
block(barArea, bar)
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/bar/IBarDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.bar
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
import me.bytebeats.views.charts.bar.BarChartData
/**
* Created by bytebeats on 2021/9/25 : 15:53
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IBarDrawer {
/**
* Draw bar
*
* @param drawScope the scope to draw
* @param canvas the Canvas to draw on
* @param barArea the bar area to draw
* @param bar the bar data
*/
fun drawBar(
drawScope: DrawScope,
canvas: Canvas,
barArea: Rect,
bar: BarChartData.Bar
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/bar/SimpleBarDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.bar
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.DrawScope
import me.bytebeats.views.charts.bar.BarChartData
/**
* Created by bytebeats on 2021/9/25 : 15:54
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
class SimpleBarDrawer : IBarDrawer {
private val mBarPaint by lazy { Paint().apply { isAntiAlias = true } }
override fun drawBar(
drawScope: DrawScope,
canvas: Canvas,
barArea: Rect,
bar: BarChartData.Bar
) {
canvas.drawRect(barArea, mBarPaint.apply { color = bar.color })
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/label/ILabelDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.label
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 13:59
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface ILabelDrawer {
/**
* Required x axis height
*
* @param drawScope the scope to draw in
* @return required height
*/
fun requiredXAxisHeight(drawScope: DrawScope): Float = 0F
/**
* Required above bar height
*
* @param drawScope the scope to draw in
* @return required height
*/
fun requiredAboveBarHeight(drawScope: DrawScope): Float = 0F
/**
* Draw label
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param label the label to draw on the axis
* @param barArea the area to draw a bar
* @param xAxisArea the x axis area to draw
*/
fun drawLabel(
drawScope: DrawScope,
canvas: Canvas,
label: Any?,
barArea: Rect,
xAxisArea: Rect
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/label/SimpleLabelDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.label
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.AxisLabelFormatter
import me.bytebeats.views.charts.toLegacyInt
import me.bytebeats.views.charts.util.FLOAT_1_5
/**
* Created by bytebeats on 2021/9/25 : 14:01
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SimpleLabelDrawer(
val drawLocation: DrawLocation = DrawLocation.Inside,
val labelTextSize: TextUnit = 12.sp,
val labelTextColor: Color = Color.Black,
val axisLabelFormatter: AxisLabelFormatter = { value -> "$value" }
) : ILabelDrawer {
private val mLabelTextArea: Float? = null
private val mPaint by lazy {
android.graphics.Paint().apply {
textAlign = android.graphics.Paint.Align.CENTER
color = labelTextColor.toLegacyInt()
}
}
override fun requiredAboveBarHeight(drawScope: DrawScope): Float = when (drawLocation) {
DrawLocation.Outside -> FLOAT_1_5 * labelTextHeight(drawScope)
else -> 0F
}
override fun requiredXAxisHeight(drawScope: DrawScope): Float = when (drawLocation) {
DrawLocation.XAxis -> labelTextHeight(drawScope)
else -> 0F
}
override fun drawLabel(
drawScope: DrawScope,
canvas: Canvas,
label: Any?,
barArea: Rect,
xAxisArea: Rect
) {
with(drawScope) {
val xCenter = barArea.left + barArea.width / 2
val yCenter = when (drawLocation) {
DrawLocation.Inside -> (barArea.top + barArea.bottom) / 2
DrawLocation.Outside -> barArea.top - labelTextSize.toPx() / 2
DrawLocation.XAxis -> barArea.bottom + labelTextHeight(drawScope)
}
val labelValue = axisLabelFormatter(label)
canvas.nativeCanvas.drawText(labelValue, xCenter, yCenter, paint(drawScope))
}
}
private fun labelTextHeight(drawScope: DrawScope): Float = with(drawScope) {
mLabelTextArea ?: (FLOAT_1_5 * labelTextSize.toPx())
}
private fun paint(drawScope: DrawScope): android.graphics.Paint = with(drawScope) {
mPaint.apply { textSize = labelTextSize.toPx() }
}
enum class DrawLocation {
Inside,
Outside,
XAxis;
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/xaxis/IXAxisDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.xaxis
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 14:16
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IXAxisDrawer {
/**
* Required height
*
* @param drawScope the draw scope to require height
* @return required height
*/
fun requiredHeight(drawScope: DrawScope): Float
/**
* Draw x axis line
*
* @param drawScope the scope to draw
* @param canvas the canvas to draw on
* @param drawableArea the area to draw a drawable
*/
fun drawXAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/xaxis/SimpleXAxisDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.xaxis
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import me.bytebeats.views.charts.util.FLOAT_1_5
/**
* Created by bytebeats on 2021/9/25 : 14:18
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SimpleXAxisDrawer(
val axisLineThickness: Dp = 1.dp,
val axisLineColor: Color = Color.Black
) : IXAxisDrawer {
private val mPaint by lazy {
Paint().apply {
isAntiAlias = true
color = axisLineColor
style = PaintingStyle.Stroke
}
}
override fun requiredHeight(drawScope: DrawScope): Float = with(drawScope) {
FLOAT_1_5 * axisLineThickness.toPx()
}
override fun drawXAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
) {
with(drawScope) {
val lineThickness = axisLineThickness.toPx()
val y = drawableArea.top + lineThickness / 2F
canvas.drawLine(
p1 = Offset(x = drawableArea.left, y = y),
p2 = Offset(x = drawableArea.right, y = y),
paint = mPaint.apply {
strokeWidth = lineThickness
})
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/yaxis/IYAxisDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.yaxis
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 14:26
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IYAxisDrawer {
/**
* Draw axis line
*
* @param drawScope the scope to draw
* @param canvas the canvas to draw on
* @param drawableArea the area to draw a drawable
*/
fun drawAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
)
/**
* Draw axis labels
*
* @param drawScope the scope to draw
* @param canvas the canvas to draw on
* @param drawableArea the drawable area
* @param minValue the min value of the y axis data
* @param maxValue the max value of the y axis data
*/
fun drawAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
minValue: Float,
maxValue: Float
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/bar/render/yaxis/SimpleYAxisDrawer.kt
================================================
package me.bytebeats.views.charts.bar.render.yaxis
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.LabelFormatter
import me.bytebeats.views.charts.toLegacyInt
import kotlin.math.roundToInt
/**
* Created by bytebeats on 2021/9/25 : 14:27
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SimpleYAxisDrawer(
val labelTextSize: TextUnit = 12.sp,
val labelTextColor: Color = Color.Black,
val drawLabelEvery: Int = 3,
val labelValueFormatter: LabelFormatter = { value -> "%.1f".format(value) },
val axisLineThickness: Dp = 1.dp,
val axisLineColor: Color = Color.Black
) : IYAxisDrawer {
private val mAxisLinePaint by lazy {
Paint().apply {
isAntiAlias = true
color = axisLineColor
style = PaintingStyle.Stroke
}
}
private val mTextPaint by lazy {
android.graphics.Paint().apply {
isAntiAlias = true
color = labelTextColor.toLegacyInt()
}
}
private val mTextBounds = android.graphics.Rect()
override fun drawAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
) {
with(drawScope) {
val lineThickness = axisLineThickness.toPx()
val x = drawableArea.right - lineThickness / 2F
canvas.drawLine(
p1 = Offset(x = x, y = drawableArea.top),
p2 = Offset(x = x, y = drawableArea.bottom),
paint = mAxisLinePaint.apply { strokeWidth = lineThickness }
)
}
}
override fun drawAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
minValue: Float,
maxValue: Float
) {
with(drawScope) {
val labelPaint = mTextPaint.apply {
textSize = labelTextSize.toPx()
textAlign = android.graphics.Paint.Align.RIGHT
}
val minLabelHeight = labelTextSize.toPx() * drawLabelEvery.toFloat()
val totalHeight = drawableArea.height
val labelCount = (drawableArea.height / minLabelHeight).roundToInt().coerceAtLeast(2)
for (i in 0..labelCount) {
val value = minValue + i * (maxValue - minValue) / labelCount
val label = labelValueFormatter(value)
val x = drawableArea.right - axisLineThickness.toPx() - labelTextSize.toPx() / 2F
labelPaint.getTextBounds(label, 0, label.length, mTextBounds)
val y =
drawableArea.bottom - i * (totalHeight / labelCount) + mTextBounds.height() / 2F
canvas.nativeCanvas.drawText(label, x, y, labelPaint)
}
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/LineChart.kt
================================================
package me.bytebeats.views.charts.line
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import me.bytebeats.views.charts.line.render.line.EmptyLineShader
import me.bytebeats.views.charts.line.render.line.ILineDrawer
import me.bytebeats.views.charts.line.render.line.ILineShader
import me.bytebeats.views.charts.line.render.line.SolidLineDrawer
import me.bytebeats.views.charts.line.render.point.FilledCircularPointDrawer
import me.bytebeats.views.charts.line.render.point.IPointDrawer
import me.bytebeats.views.charts.line.render.xaxis.IXAxisDrawer
import me.bytebeats.views.charts.line.render.xaxis.SimpleXAxisDrawer
import me.bytebeats.views.charts.line.render.yaxis.IYAxisDrawer
import me.bytebeats.views.charts.line.render.yaxis.SimpleYAxisDrawer
import me.bytebeats.views.charts.simpleChartAnimation
/**
* Created by bytebeats on 2021/9/25 : 12:55
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@Composable
fun LineChart(
lineChartData: LineChartData,
modifier: Modifier = Modifier,
animation: AnimationSpec = simpleChartAnimation(),
pointDrawer: IPointDrawer = FilledCircularPointDrawer(),
lineDrawer: ILineDrawer = SolidLineDrawer(),
lineShader: ILineShader = EmptyLineShader,
xAxisDrawer: IXAxisDrawer = SimpleXAxisDrawer(),
yAxisDrawer: IYAxisDrawer = SimpleYAxisDrawer(),
horizontalOffset: Float = 5F,
) {
check(horizontalOffset in 0F..25F) {
"Horizontal Offset is the percentage offset from side, and must be between 0 and 25, included."
}
val transitionAnimation = remember(lineChartData.points) {
Animatable(initialValue = 0F)
}
LaunchedEffect(lineChartData.points) {
transitionAnimation.snapTo(0F)
transitionAnimation.animateTo(1F, animationSpec = animation)
}
Canvas(
modifier = modifier.fillMaxSize()
) {
drawIntoCanvas { canvas ->
val yAxisDrawableArea = computeYAxisDrawableArea(
xAxisLabelSize = xAxisDrawer.requireHeight(this),
size = size
)
val xAxisDrawableArea = computeXAxisDrawableArea(
yAxisWidth = yAxisDrawableArea.width,
labelHeight = xAxisDrawer.requireHeight(this),
size = size
)
val xAxisLabelsDrawableArea = computeXAxisLabelsDrawableArea(
xAxisDrawableArea = xAxisDrawableArea,
offset = horizontalOffset
)
val chartDrawableArea = computeDrawableArea(
xAxisDrawableArea = xAxisDrawableArea,
yAxisDrawableArea = yAxisDrawableArea,
size = size,
offset = horizontalOffset
)
lineDrawer.drawLine(
drawScope = this,
canvas = canvas,
linePath = computeLinePath(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
)
)
lineShader.fillLine(
drawScope = this,
canvas = canvas,
fillPath = computeFillPath(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
)
)
lineChartData.points.forEachIndexed { index, point ->
withProgress(
index = index,
lineChartData = lineChartData,
transitionProgress = transitionAnimation.value
) {
pointDrawer.drawPoint(
drawScope = this,
canvas = canvas,
center = computePointLocation(
drawableArea = chartDrawableArea,
lineChartData = lineChartData,
point = point,
index = index
)
)
}
}
xAxisDrawer.drawXAxisLine(
drawScope = this,
drawableArea = xAxisDrawableArea,
canvas = canvas
)
xAxisDrawer.drawXAxisLabels(
drawScope = this,
canvas = canvas,
drawableArea = xAxisLabelsDrawableArea,
labels = lineChartData.points.map { it.label })
yAxisDrawer.drawAxisLine(
drawScope = this,
drawableArea = yAxisDrawableArea,
canvas = canvas
)
yAxisDrawer.drawAxisLabels(
drawScope = this,
canvas = canvas,
drawableArea = yAxisDrawableArea,
minValue = lineChartData.minY,
maxValue = lineChartData.maxY
)
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/LineChartData.kt
================================================
package me.bytebeats.views.charts.line
import me.bytebeats.views.charts.util.FLOAT_100
/**
* Created by bytebeats on 2021/9/24 : 19:39
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class LineChartData(
val points: List,
val padBy: Float = 20F,// percentage we pad yValue by
val startAtZero: Boolean = false
) {
init {
require(padBy in 0F..FLOAT_100) {
"padBy must be between 0F and 100F, included"
}
}
private val yMinMaxValues: Pair
get() {
val minValue = points.minOf { it.value }
val maxValue = points.maxOf { it.value }
return minValue to maxValue
}
val maxY: Float
get() = yMinMaxValues.second + (yMinMaxValues.second - yMinMaxValues.first) * padBy / FLOAT_100
val minY: Float
get() = if (startAtZero) 0F
else yMinMaxValues.first - (yMinMaxValues.second - yMinMaxValues.first) * padBy / FLOAT_100
val yRange: Float
get() = maxY - minY
data class Point(
val value: Float,
val label: String
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/LineCharts.kt
================================================
package me.bytebeats.views.charts.line
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import me.bytebeats.views.charts.util.FLOAT_10
import me.bytebeats.views.charts.util.FLOAT_100
/**
* Created by bytebeats on 2021/9/24 : 19:26
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
internal fun computeDrawableArea(
xAxisDrawableArea: Rect,
yAxisDrawableArea: Rect,
size: Size,
offset: Float
): Rect {
val horizontalOffset = xAxisDrawableArea.width * offset / FLOAT_100
return Rect(
left = yAxisDrawableArea.right + horizontalOffset,
top = 0F,
bottom = xAxisDrawableArea.top,
right = size.width - horizontalOffset
)
}
internal fun computeXAxisDrawableArea(
yAxisWidth: Float,
labelHeight: Float,
size: Size
): Rect {
val top = size.height - labelHeight
return Rect(
left = yAxisWidth,
top = top,
right = size.width,
bottom = size.height
)
}
internal fun computeXAxisLabelsDrawableArea(
xAxisDrawableArea: Rect,
offset: Float
): Rect {
val horizontalOffset = xAxisDrawableArea.width * offset / FLOAT_100
return Rect(
left = xAxisDrawableArea.left + horizontalOffset,
top = xAxisDrawableArea.top,
right = xAxisDrawableArea.right - horizontalOffset,
bottom = xAxisDrawableArea.bottom
)
}
internal fun Density.computeYAxisDrawableArea(
xAxisLabelSize: Float,
size: Size
): Rect {
val right =
50.dp.toPx().coerceAtMost(size.width * FLOAT_10 / FLOAT_100) // 50dp or 10% of chart view width
return Rect(
left = 0F,
top = 0F,
right = right,
bottom = size.height - xAxisLabelSize
)
}
internal fun computePointLocation(
drawableArea: Rect,
lineChartData: LineChartData,
point: LineChartData.Point,
index: Int
): Offset {
val dx = index.toFloat() / (lineChartData.points.size - 1)
val dy = (point.value - lineChartData.minY) / lineChartData.yRange
return Offset(
x = dx * drawableArea.width + drawableArea.left,
y = drawableArea.height - dy * drawableArea.height
)
}
internal fun withProgress(
index: Int,
lineChartData: LineChartData,
transitionProgress: Float,
progressListener: (progress: Float) -> Unit
) {
val size = lineChartData.points.size
val toIndex = (size * transitionProgress).toInt() + 1
if (index == toIndex) {
val sizeF = lineChartData.points.size.toFloat()
val divider = 1F / sizeF
val down = (index - 1) * divider
progressListener((transitionProgress - down) / divider)
} else if (index < toIndex) {
progressListener(1F)
}
}
internal fun computeLinePath(
drawableArea: Rect,
lineChartData: LineChartData,
transitionProgress: Float
): Path = Path().apply {
var prePointLocation: Offset? = null
lineChartData.points.forEachIndexed { index, point ->
withProgress(index, lineChartData, transitionProgress) { progress ->
val pointLocation = computePointLocation(drawableArea, lineChartData, point, index)
if (index == 0) {
moveTo(pointLocation.x, pointLocation.y)
} else {
if (progress <= 1F) {
val preX = prePointLocation?.x ?: 0F
val preY = prePointLocation?.y ?: 0F
val tx = (pointLocation.x - preX) * progress + preX
val ty = (pointLocation.y - preY) * progress + preY
lineTo(tx, ty)
} else {
lineTo(pointLocation.x, pointLocation.y)
}
}
prePointLocation = pointLocation
}
}
}
internal fun computeFillPath(
drawableArea: Rect,
lineChartData: LineChartData,
transitionProgress: Float
): Path = Path().apply {
moveTo(drawableArea.left, drawableArea.bottom)
var prePointX: Float? = null
var prePointLocation: Offset? = null
lineChartData.points.forEachIndexed { index, point ->
withProgress(index, lineChartData, transitionProgress) { progress ->
val pointLocation = computePointLocation(drawableArea, lineChartData, point, index)
if (index == 0) {
lineTo(drawableArea.left, pointLocation.y)
lineTo(pointLocation.x, pointLocation.y)
} else {
prePointX = if (progress <= 1F) {
val preX = prePointLocation?.x ?: 0F
val preY = prePointLocation?.y ?: 0F
val tx = (pointLocation.x - preX) * progress + preX
val ty = (pointLocation.y - preY) * progress + preY
lineTo(tx, ty)
tx
} else {
lineTo(pointLocation.x, pointLocation.y)
pointLocation.x
}
}
prePointLocation = pointLocation
}
}
prePointX?.let {
lineTo(it, drawableArea.bottom)
lineTo(drawableArea.left, drawableArea.bottom)
} ?: lineTo(drawableArea.left, drawableArea.bottom)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/EmptyLineShader.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 12:50
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
object EmptyLineShader : ILineShader {
override fun fillLine(
drawScope: DrawScope,
canvas: Canvas,
fillPath: Path
) {
// do nothing here
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/GradientLineShader.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 12:52
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class GradientLineShader(
val colors: List = listOf(Color.Blue, Color.Transparent)
) : ILineShader {
private val mBrush = Brush.verticalGradient(colors)
override fun fillLine(
drawScope: DrawScope,
canvas: Canvas,
fillPath: Path
) {
drawScope.drawPath(path = fillPath, brush = mBrush)
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/ILineDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 12:45
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface ILineDrawer {
/**
* Draw line
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param linePath the line path to draw
*/
fun drawLine(
drawScope: DrawScope,
canvas: Canvas,
linePath: Path
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/ILineShader.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 12:49
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface ILineShader {
/**
* fill slice
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param fillPath the path to fill
*/
fun fillLine(
drawScope: DrawScope,
canvas: Canvas,
fillPath: Path
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/SolidLineDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Created by bytebeats on 2021/9/25 : 12:46
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SolidLineDrawer(
val thickness: Dp = 3.dp,
val color: Color = Color.Cyan
) : ILineDrawer {
private val mPaint by lazy {
Paint().apply {
color = this@SolidLineDrawer.color
style = PaintingStyle.Stroke
isAntiAlias = true
}
}
override fun drawLine(
drawScope: DrawScope,
canvas: Canvas,
linePath: Path
) {
val lineThickness = with(drawScope) {
thickness.toPx()
}
canvas.drawPath(
path = linePath,
paint = mPaint.apply { strokeWidth = lineThickness }
)
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/line/SolidLineShader.kt
================================================
package me.bytebeats.views.charts.line.render.line
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/25 : 12:51
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SolidLineShader(
val color: Color = Color.Blue
) : ILineShader {
override fun fillLine(
drawScope: DrawScope,
canvas: Canvas,
fillPath: Path
) {
drawScope.drawPath(
path = fillPath,
color = color
)
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/point/EmptyPointDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.point
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/24 : 20:24
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
object EmptyPointDrawer : IPointDrawer {
override fun drawPoint(
drawScope: DrawScope,
canvas: Canvas,
center: Offset
) {
//empty point, we do nothing here.
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/point/FilledCircularPointDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.point
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Created by bytebeats on 2021/9/24 : 20:34
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class FilledCircularPointDrawer(
val diameter: Dp = 8.dp,
val color: Color = Color.Blue
) : IPointDrawer {
private val mPaint by lazy {
Paint().apply {
color = this@FilledCircularPointDrawer.color
style = PaintingStyle.Fill
isAntiAlias = true
}
}
override fun drawPoint(
drawScope: DrawScope,
canvas: Canvas,
center: Offset
) {
with(drawScope as Density) {
canvas.drawCircle(center, diameter.toPx() / 2F, paint = mPaint)
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/point/HollowCircularPointDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.point
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Created by bytebeats on 2021/9/24 : 20:38
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class HollowCircularPointDrawer(
val diameter: Dp = 8.dp,
val lineThickness: Dp = 2.dp,
val color: Color = Color.Blue
) : IPointDrawer {
private val mPaint by lazy {
Paint().apply {
color = this@HollowCircularPointDrawer.color
style = PaintingStyle.Stroke
isAntiAlias = true
}
}
override fun drawPoint(
drawScope: DrawScope,
canvas: Canvas,
center: Offset
) {
with(drawScope as Density) {
canvas.drawCircle(
center = center,
radius = diameter.toPx() / 2F,
paint = mPaint.apply { strokeWidth = lineThickness.toPx() })
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/point/IPointDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.point
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/24 : 20:22
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IPointDrawer {
/**
* Draw point
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param center the center point to draw a point
*/
fun drawPoint(
drawScope: DrawScope,
canvas: Canvas,
center: Offset
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/xaxis/IXAxisDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.xaxis
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/24 : 20:45
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IXAxisDrawer {
/**
* require height
*
* @param drawScope the scope to draw in
*/
fun requireHeight(drawScope: DrawScope): Float
/**
* Draw x axis line
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param drawableArea the area to draw a drawable
*/
fun drawXAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
)
/**
* Draw labels in x axis
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param drawableArea the area to draw
* @param labels the labels to draw on axis
*/
fun drawXAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
labels: List<*>
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/xaxis/SimpleXAxisDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.xaxis
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.AxisLabelFormatter
import me.bytebeats.views.charts.toLegacyInt
import me.bytebeats.views.charts.util.FLOAT_1_5
/**
* Created by bytebeats on 2021/9/24 : 20:50
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SimpleXAxisDrawer(
val labelTextSize: TextUnit = 12.sp,
val labelTextColor: Color = Color.Black,
val drawLabelEvery: Int = 1,// draw label text every $drawLabelEvery, like 1, 2, 3 and so on.
val axisLineThickness: Dp = 1.dp,
val axisLineColor: Color = Color.Black,
val axisLabelFormatter: AxisLabelFormatter = { value -> "$value" }
) : IXAxisDrawer {
private val mAxisLinePaint by lazy {
Paint().apply {
isAntiAlias = true
color = axisLineColor
style = PaintingStyle.Stroke
}
}
private val mTextPaint by lazy {
android.graphics.Paint().apply {
isAntiAlias = true
color = labelTextColor.toLegacyInt()
}
}
override fun requireHeight(drawScope: DrawScope): Float = with(drawScope) {
FLOAT_1_5 * (labelTextSize.toPx() + axisLineThickness.toPx())
}
override fun drawXAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
) {
with(drawScope) {
val lineThickness = axisLineThickness.toPx()
val y = drawableArea.top + lineThickness / 2F
canvas.drawLine(
p1 = Offset(x = drawableArea.left, y = y),
p2 = Offset(x = drawableArea.right, y = y),
paint = mAxisLinePaint.apply { strokeWidth = lineThickness })
}
}
override fun drawXAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
labels: List<*>
) {
with(drawScope) {
val labelPaint = mTextPaint.apply {
textSize = labelTextSize.toPx()
textAlign = android.graphics.Paint.Align.CENTER
}
val labelIncrements = drawableArea.width / (labels.size - 1)
labels.forEachIndexed { index, label ->
if (index.rem(drawLabelEvery) == 0) {
val labelValue = axisLabelFormatter(label)
val x = drawableArea.left + labelIncrements * index
val y = drawableArea.bottom
canvas.nativeCanvas.drawText(labelValue, x, y, labelPaint)
}
}
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/yaxis/IYAxisDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.yaxis
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Created by bytebeats on 2021/9/24 : 21:06
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface IYAxisDrawer {
/**
* Draw axis line
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param drawableArea the area to draw a drawable
*/
fun drawAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
)
/**
* Draw axis labels
*
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param drawableArea the area to draw
* @param minValue the min value of the y axis data
* @param maxValue the max value of the y axis data
*/
fun drawAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
minValue: Float,
maxValue: Float
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/line/render/yaxis/SimpleYAxisDrawer.kt
================================================
package me.bytebeats.views.charts.line.render.yaxis
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.bytebeats.views.charts.LabelFormatter
import me.bytebeats.views.charts.toLegacyInt
import kotlin.math.roundToInt
/**
* Created by bytebeats on 2021/9/24 : 21:08
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class SimpleYAxisDrawer(
val labelTextSize: TextUnit = 12.sp,
val labelTextColor: Color = Color.Black,
val drawLabelEvery: Int = 1,
val labelValueFormatter: LabelFormatter = { value -> "%.1f".format(value) },
val axisLineThickness: Dp = 1.dp,
val axisLineColor: Color = Color.Black,
) : IYAxisDrawer {
private val mAxisLinePaint by lazy {
Paint().apply {
isAntiAlias = true
color = axisLineColor
style = PaintingStyle.Stroke
}
}
private val mTextPaint by lazy {
android.graphics.Paint().apply {
isAntiAlias = true
color = labelTextColor.toLegacyInt()
}
}
private val mTextBounds = android.graphics.Rect()
override fun drawAxisLine(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect
) = with(drawScope) {
val lineThickness = axisLineThickness.toPx()
val x = drawableArea.right - lineThickness / 2F
canvas.drawLine(
p1 = Offset(x = x, y = drawableArea.top),
p2 = Offset(x = x, y = drawableArea.bottom),
paint = mAxisLinePaint.apply { strokeWidth = lineThickness })
}
override fun drawAxisLabels(
drawScope: DrawScope,
canvas: Canvas,
drawableArea: Rect,
minValue: Float,
maxValue: Float
) {
with(drawScope) {
val labelPaint = mTextPaint.apply {
textSize = labelTextSize.toPx()
textAlign = android.graphics.Paint.Align.RIGHT
}
val minLabelHeight = labelTextSize.toPx() * drawLabelEvery.toFloat()
val totalHeight = drawableArea.height
val labelCount = (drawableArea.height / minLabelHeight).roundToInt().coerceAtLeast(2)
for (i in 0..labelCount) {
val value = minValue + i * (maxValue - minValue) / labelCount
val label = labelValueFormatter(value)
val x = drawableArea.right - axisLineThickness.toPx() - labelTextSize.toPx() / 2F
labelPaint.getTextBounds(label, 0, label.length, mTextBounds)
val y =
drawableArea.bottom - i * (totalHeight / labelCount) - mTextBounds.height() / 2F
canvas.nativeCanvas.drawText(label, x, y, labelPaint)
}
}
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/pie/PieChart.kt
================================================
package me.bytebeats.views.charts.pie
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.tooling.preview.Preview
import me.bytebeats.views.charts.pie.render.ISliceDrawer
import me.bytebeats.views.charts.pie.render.SimpleSliceDrawer
import me.bytebeats.views.charts.simpleChartAnimation
/**
* Created by bytebeats on 2021/9/24 : 15:34
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
@Composable
fun PieChart(
pieChartData: PieChartData,
modifier: Modifier = Modifier,
animation: AnimationSpec = simpleChartAnimation(),
sliceDrawer: ISliceDrawer = SimpleSliceDrawer()
) {
val transitionProgress = remember(pieChartData.slices) {
Animatable(initialValue = 0F)
}
LaunchedEffect(pieChartData.slices) {
transitionProgress.animateTo(1F, animationSpec = animation)
}
DrawChart(
pieChartData = pieChartData,
modifier = modifier.fillMaxSize(),
progress = transitionProgress.value,
sliceDrawer = sliceDrawer
)
}
@Composable
private fun DrawChart(
pieChartData: PieChartData,
modifier: Modifier,
progress: Float,
sliceDrawer: ISliceDrawer
) {
val slices = pieChartData.slices
Canvas(
modifier = modifier
) {
drawIntoCanvas {
var startArc = 0F
slices.forEach { slice ->
val arc = calculateAngle(
sliceLength = slice.value,
totalLength = pieChartData.totalLength,
progress = progress
)
sliceDrawer.drawSlice(
drawScope = this,
canvas = drawContext.canvas,
area = size,
startAngle = startArc,
sweepAngle = arc,
slice = slice
)
startArc += arc
}
}
}
}
@Preview
@Composable
private fun PieChartPreview() = PieChart(
pieChartData = PieChartData(
slices = listOf(
PieChartData.Slice(25F, Color.Red),
PieChartData.Slice(45F, Color.Green),
PieChartData.Slice(20F, Color.Blue),
)
)
)
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/pie/PieChartData.kt
================================================
package me.bytebeats.views.charts.pie
import androidx.compose.ui.graphics.Color
/**
* Created by bytebeats on 2021/9/24 : 14:32
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
data class PieChartData(val slices: List) {
internal val totalLength: Float
get() {
return slices.map { it.value }.sum()
}
data class Slice(
val value: Float,
val color: Color
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/pie/PieCharts.kt
================================================
package me.bytebeats.views.charts.pie
import me.bytebeats.views.charts.util.FLOAT_360
/**
* Created by bytebeats on 2021/9/24 : 14:27
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
internal fun calculateAngle(
sliceLength: Float,
totalLength: Float,
progress: Float
): Float = FLOAT_360 * sliceLength * progress / totalLength
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/pie/render/ISliceDrawer.kt
================================================
package me.bytebeats.views.charts.pie.render
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.drawscope.DrawScope
import me.bytebeats.views.charts.pie.PieChartData
/**
* Created by bytebeats on 2021/9/24 : 14:30
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
interface ISliceDrawer {
/**
* Draw slice
* @param drawScope the scope to draw in
* @param canvas the canvas to draw on
* @param area the area to draw
* @param startAngle the start angle to draw a slice
* @param sweepAngle the sweep angle to draw a slice
* @param slice the slice data to draw
*/
fun drawSlice(
drawScope: DrawScope,
canvas: Canvas,
area: Size,
startAngle: Float,
sweepAngle: Float,
slice: PieChartData.Slice
)
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/pie/render/SimpleSliceDrawer.kt
================================================
package me.bytebeats.views.charts.pie.render
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.PaintingStyle
import androidx.compose.ui.graphics.drawscope.DrawScope
import me.bytebeats.views.charts.pie.PieChartData
import me.bytebeats.views.charts.util.FLOAT_200
/**
* Created by bytebeats on 2021/9/24 : 14:36
* E-mail: happychinapc@gmail.com
* Quote: Peasant. Educated. Worker
*/
class SimpleSliceDrawer(private val sliceThickness: Float = 25F) : ISliceDrawer {
init {
require(sliceThickness in 10F..100F) {
"Thickness must be between 10 and 100, included"
}
}
private val sectorPaint by lazy {
Paint().apply {
isAntiAlias = true
style = PaintingStyle.Stroke
}
}
private fun computeSectorThickness(area: Size): Float {
val minSize = area.width.coerceAtMost(area.height)
return sliceThickness * minSize / FLOAT_200
}
private fun computeDrawableArea(area: Size): Rect {
val sliceThicknessOffset = computeSectorThickness(area) / 2F
val horizontalOffset = (area.width - area.height) / 2F
return Rect(
left = sliceThicknessOffset + horizontalOffset,
top = sliceThicknessOffset,
right = area.width - sliceThicknessOffset - horizontalOffset,
bottom = area.height - sliceThicknessOffset
)
}
override fun drawSlice(
drawScope: DrawScope,
canvas: Canvas,
area: Size,
startAngle: Float,
sweepAngle: Float,
slice: PieChartData.Slice
) {
val sliceThickness = computeSectorThickness(area)
val drawableArea = computeDrawableArea(area)
canvas.drawArc(
rect = drawableArea,
paint = sectorPaint.apply {
color = slice.color
strokeWidth = sliceThickness
},
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = false,
)
}
}
================================================
FILE: charts/src/main/java/me/bytebeats/views/charts/util/Floats.kt
================================================
package me.bytebeats.views.charts.util
internal const val FLOAT_10 = 10F
internal const val FLOAT_100 = 100F
internal const val FLOAT_200 = 200F
internal const val FLOAT_255 = 255F
internal const val FLOAT_360 = 360F
internal const val FLOAT_1_5 = 1.5F
internal const val FLOAT_0_5 = .5F
================================================
FILE: config/detekt/detekt.yml
================================================
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
checkExhaustiveness: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors:
active: true
exclude:
- 'DetektProgressListener'
- 'KtFileCountProcessor'
- 'PackageCountProcessor'
- 'ClassCountProcessor'
- 'FunctionCountProcessor'
- 'PropertyCountProcessor'
- 'ProjectComplexityProcessor'
- 'ProjectCognitiveComplexityProcessor'
- 'ProjectLLOCProcessor'
- 'ProjectCLOCProcessor'
- 'ProjectLOCProcessor'
- 'ProjectSLOCProcessor'
- 'LicenseHeaderLoaderExtension'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FindingsReport'
- 'FileBasedFindingsReport'
# - 'LiteFindingsReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
- 'SarifOutputReport'
comments:
active: true
AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
KDocReferencesNonPublicProperty:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
OutdatedDocumentation:
active: false
matchTypeParameters: true
matchDeclarationsOrder: true
allowParamOnConstructorProperties: false
UndocumentedPublicClass:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchInNestedClass: false
searchInInnerClass: false
searchInInnerObject: true
searchInInnerInterface: true
searchInProtectedClass: false
UndocumentedPublicFunction:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedFunction: false
ignoreAnnotated:
- 'Composable'
- 'Preview'
ignoreFunction:
- '[a-z][a-zA-Z0-9]*'
UndocumentedPublicProperty:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedProperty: false
complexity:
active: true
CognitiveComplexMethod:
active: false
threshold: 15
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ignoreOverloaded: false
CyclomaticComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: [ ]
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 100
LongParameterList:
active: true
functionThreshold: 30
constructorThreshold: 10
ignoreDefaultParameters: true
ignoreDataClasses: true
ignoreAnnotatedParameter: [ ]
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: true
threshold: 3
ignoreArgumentsMatchingNames: true
NestedBlockDepth:
active: true
threshold: 4
NestedScopeFunctions:
active: true
threshold: 3
functions:
- 'kotlin.apply'
- 'kotlin.run'
- 'kotlin.with'
- 'kotlin.let'
- 'kotlin.also'
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/internal/**' ]
thresholdInFiles: 20
thresholdInClasses: 30
thresholdInInterfaces: 20
thresholdInObjects: 20
thresholdInEnums: 20
ignoreDeprecated: false
ignorePrivate: true
ignoreOverridden: true
ignoreAnnotatedFunctions: [ 'Preview' ]
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: true
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: true
SleepInsteadOfDelay:
active: true
SuspendFunSwallowedCancellation:
active: false
SuspendFunWithCoroutineScopeReceiver:
active: false
SuspendFunWithFlowReturnType:
active: true
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
NotImplementedDeclaration:
active: true
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
naming:
active: true
BooleanPropertyNaming:
active: true
allowedPattern: '^(is|has|are)'
ClassNaming:
active: true
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
EnumNaming:
active: true
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: [ ]
FunctionMaxLength:
active: true
maximumFunctionNameLength: 30
FunctionMinLength:
active: true
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
functionPattern: '[a-zA-Z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
ignoreAnnotated:
- 'Composable'
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
InvalidPackageDeclaration:
active: true
rootPackage: ''
requireRootInDeclaration: false
LambdaParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NoNameShadowing:
active: true
NonBooleanPropertyPrefixedWithIs:
active: true
ObjectPropertyNaming:
active: true
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: true
maximumVariableNameLength: 64
VariableMinLength:
active: true
minimumVariableNameLength: 1
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
performance:
active: true
ArrayPrimitive:
active: true
CouldBeSequence:
active: false
threshold: 3
ForEachOnRange:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
SpreadOperator:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnnecessaryPartOfBinaryExpression:
active: true
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: true
forbiddenTypePatterns:
- 'kotlin.String'
CastNullableToNonNullableType:
active: true
CastToNullableType:
active: true
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: true
mutableTypes:
- 'kotlin.collections.MutableList'
- 'kotlin.collections.MutableMap'
- 'kotlin.collections.MutableSet'
- 'java.util.ArrayList'
- 'java.util.LinkedHashSet'
- 'java.util.HashSet'
- 'java.util.LinkedHashMap'
- 'java.util.HashMap'
ElseCaseInsteadOfExhaustiveWhen:
active: true
ignoredSubjectTypes: [ ]
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: true
IgnoredReturnValue:
active: true
restrictToConfig: true
returnValueAnnotations:
- 'CheckResult'
- '*.CheckResult'
- 'CheckReturnValue'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- 'CanIgnoreReturnValue'
- '*.CanIgnoreReturnValue'
returnValueTypes:
- 'kotlin.sequences.Sequence'
- 'kotlinx.coroutines.flow.*Flow'
- 'java.util.stream.*Stream'
ignoreFunctionCall: [ ]
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: true
MissingPackageDeclaration:
active: false
excludes: [ '**/*.kts' ]
NullCheckOnMutableProperty:
active: false
NullableToStringCall:
active: false
PropertyUsedBeforeDeclaration:
active: false
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullCheck:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnsafeCast:
active: true
UnusedUnaryOperator:
active: true
UselessPostfixExpression:
active: true
WrongEqualsTypeParameter:
active: true
style:
active: true
AlsoCouldBeApply:
active: false
BracesOnIfStatements:
active: false
singleLine: 'never'
multiLine: 'always'
BracesOnWhenStatements:
active: false
singleLine: 'necessary'
multiLine: 'consistent'
CanBeNonNullable:
active: false
CascadingCallWrapping:
active: false
includeElvis: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix:
- 'to'
allowOperators: false
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: true
maxDestructuringEntries: 3
DoubleNegativeLambda:
active: false
negativeFunctions:
- reason: 'Use `takeIf` instead.'
value: 'takeUnless'
- reason: 'Use `all` instead.'
value: 'none'
negativeFunctionNameParts:
- 'not'
- 'non'
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: true
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenAnnotation:
active: false
annotations:
- reason: 'it is a java annotation. Use `Suppress` instead.'
value: 'java.lang.SuppressWarnings'
- reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
value: 'java.lang.Deprecated'
- reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
value: 'java.lang.annotation.Documented'
- reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
value: 'java.lang.annotation.Target'
- reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
value: 'java.lang.annotation.Retention'
- reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
value: 'java.lang.annotation.Repeatable'
- reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
value: 'java.lang.annotation.Inherited'
ForbiddenComment:
active: true
comments:
- reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
value: 'FIXME:'
- reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
value: 'STOPSHIP:'
- reason: 'Forbidden TODO todo marker in comment, please do the changes.'
value: 'TODO:'
allowedPatterns: ''
ForbiddenImport:
active: false
imports: [ ]
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- reason: 'print does not allow you to configure the output stream. Use a logger instead.'
value: 'kotlin.io.print'
- reason: 'println does not allow you to configure the output stream. Use a logger instead.'
value: 'kotlin.io.println'
ForbiddenSuppress:
active: false
rules: [ ]
ForbiddenVoid:
active: true
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: [ ]
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ]
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: true
ignoreLocalVariableDeclaration: true
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: true
ignoreNamedArgument: true
ignoreEnums: true
ignoreRanges: true
ignoreExtensionFunctions: true
ignoreAnnotated:
- 'Preview'
MandatoryBracesLoops:
active: false
MaxChainedCallsOnSameLine:
active: false
maxChainedCalls: 5
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
excludeRawStrings: true
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
active: false
MultilineRawStringIndentation:
active: false
indentSize: 4
trimmingMethods:
- 'trimIndent'
- 'trimMargin'
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
NullableBooleanCheck:
active: false
ObjectLiteralToLambda:
active: true
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: true
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions:
- 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
StringShouldBeRawString:
active: false
maxEscapedCharacterCount: 2
ignoredCharacters: [ ]
ThrowsCount:
active: true
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
TrimMultilineRawString:
active: false
trimmingMethods:
- 'trimIndent'
- 'trimMargin'
UnderscoresInNumericLiterals:
active: false
acceptableLength: 4
allowNonStandardGrouping: false
UnnecessaryAbstractClass:
active: true
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryBackticks:
active: false
UnnecessaryBracesAroundTrailingLambda:
active: false
UnnecessaryFilter:
active: true
UnnecessaryInheritance:
active: true
UnnecessaryInnerClass:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
allowForUnclearPrecedence: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedParameter:
active: true
allowedNames: 'ignored|expected'
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: ''
ignoreAnnotated:
- 'Preview'
UnusedPrivateProperty:
active: true
allowedNames: '_|ignored|expected|serialVersionUID'
UseAnyOrNoneInsteadOfFind:
active: true
UseArrayLiteralsInAnnotations:
active: true
UseCheckNotNull:
active: true
UseCheckOrError:
active: true
UseDataClass:
active: false
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
ignoreWhenContainingVariableDeclaration: false
UseIsNullOrEmpty:
active: true
UseLet:
active: false
UseOrEmpty:
active: true
UseRequire:
active: true
UseRequireNotNull:
active: true
UseSumOfInsteadOfFlatMapSize:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
ignoreLateinitVar: false
WildcardImport:
active: true
excludeImports:
- 'java.util.*'
================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
agp = "8.3.2"
composeCharts = "0.2.1"
kotlin = "1.9.24"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.8.1"
activityCompose = "1.9.0"
compose = "1.6.7"
material3 = "1.2.1"
ktCompilerExt = "1.5.14"
dokka = "1.9.20"
detekt = "1.23.6"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
compose-charts = { module = "io.github.bytebeats:compose-charts", version.ref = "composeCharts" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "compose" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "compose" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "compose" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
detekt-gradle-plugin = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Sep 23 21:39:47 CST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
================================================
FILE: gradle.properties
================================================
# 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
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=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
# POM related configuration
GROUP_ID=io.github.bytebeats
# developer related configuration
DEVELOPER_ID=bytebeats
DEVELOPER_NAME=Chen Pan
DEVELOPER_URL=https://github.com/bytebeats
DEVELOPER_EMAIL=happychinapc@gmail.com
# SCM related configuration
SCM_CONNECTION=scm:git:github.com/bytebeats/compose-charts.git
SCM_DEVELOPER_CONNECTION=scm:git:ssh:github.com/bytebeats/compose-charts.git
SCM_URL=https://github.com/bytebeats/compose-charts/tree/master
# License related configuration
LICENSE_NAME=The Apache License, Version 2.0
LICENSE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
LICENCE_DIST=repo
# License related configuration
ISSUE_SYSTEM=GitHub
ISSUE_URL=https://github.com/bytebeats/compose-charts/issues
# organization related configuration
#ORGANIZATION_NAME=
#ORGANIZATION_URL=
# Contributors related configuration
CONTRIBUTOR_NAME=bytebeats
CONTRIBUTOR_EMAIL=happychinapc@gmail.com
CONTRIBUTOR_URL=https://github.com/bytebeats
CONTRIBUTOR_TIMEZONE=China/Beijing
# CI related configuration
CI_SYSTEM=GitHub
CI_URL=https://github.com/bytebeats/compose-charts/actions/
# Library related configuration
PUBLICATION_NAME=ComposeCharts
MODULE_NAME=ComposeCharts
REPO_NAME=SonaTypeMavenCentral
COMPOSE_CHARTS_ARTIFACT_ID=compose-charts
COMPOSE_CHARTS_VERSION=0.2.1
COMPOSE_CHARTS_PACKAGING=aar
COMPOSE_CHARTS_INCEPTION_YEAR=2021
COMPOSE_CHARTS_URL=https://github.com/bytebeats/compose-charts
COMPOSE_CHARTS_DESCRIPTION=compose-charts: Simple Jetpack Compose Charts for multi-platform. Including Android, Web, Desktop.
RELEASES_REPO_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
SNAPSHOTS_REPO_URL=https://s01.oss.sonatype.org/content/repositories/snapshots/
================================================
FILE: gradlew
================================================
#!/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" "$@"
================================================
FILE: gradlew.bat
================================================
@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
================================================
FILE: settings.gradle.kts
================================================
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
maven {
url = uri("https://repo1.maven.org/maven2/")
}
mavenLocal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "compose-charts"
include(":app")
include(":charts")