This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at hello2himanshusingh@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+31
View File
@@ -0,0 +1,31 @@
# Contributing to Charty
Welcome to Charty! We appreciate your interest in contributing to this open-source project. By contributing, you can help improve the library, add new features, fix issues, and make Charty even better for the community.
## How Can You Contribute?
- **Reporting Issues**: If you encounter any bugs or issues while using Charty, please let us know by creating a new issue in the [issue tracker](https://github.com/hi-manshu/Charty/issues) . Provide a detailed description, steps to reproduce, and any relevant information to help us investigate and address the problem.
- **Feature Requests**: Have a great idea for a new feature or improvement? We'd love to hear it! Open a new issue in the [issue tracker](https://github.com/hi-manshu/Charty/issues) and clearly explain the proposed feature or enhancement. We'll review your suggestion and provide feedback.
- **Pull Requests**: Want to contribute code to Charty? You can submit a pull request with your changes. Please follow the guidelines below:
- Fork the repository and create a new branch for your changes.
- Ensure your code adheres to the project's coding style and conventions.
- Write clear, concise, and meaningful commit messages.
- Include tests for your changes whenever applicable. Tests help ensure the stability and reliability of the library.
- Update the documentation to reflect any changes or additions.
- Submit the pull request, providing a detailed description of your changes and their purpose.
- Documentation Improvements: You can help improve the Charty documentation by fixing typos, clarifying explanations, adding examples, or expanding on existing content. Submit a pull request with your documentation changes, ensuring they follow the same guidelines as code contributions.
## Code Style and Conventions
To maintain consistency within the project, please adhere to the following guidelines:
- Follow the existing code style and formatting conventions.
- Use meaningful variable and function names that reflect their purpose.
- Add comments to explain complex logic or provide context when necessary.
- Write clear and concise code that is easy to understand and maintain.
- Ensure your code is well-tested, following the existing testing patterns and conventions.
- Try running `./gradlew detekt` and `./gradlew ktlintCheck`
## Code of Conduct
Contributors are expected to adhere to the Code of Conduct when participating in the Charty project. Be respectful, inclusive, and considerate of others. Any form of harassment, discrimination, or abusive behavior will not be tolerated.
## Getting Help
If you need any assistance, have questions, or require further clarification, feel free to reach out by creating a new issue or contacting the project maintainers.
Thank you for your contributions and support in making Charty a valuable tool for data visualization in the Android community!
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2023 Charty Contributors
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
http://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.
+67
View File
@@ -0,0 +1,67 @@
## Charty : Elementary Chart library for Compose
![Charty](image/charty-banner.png)
Chart Library built using Jetpack Compose and is highly customizable.
_Made with ❤️ for Android Developers by Himanshu_
[![Github Followers](https://img.shields.io/github/followers/hi-manshu?label=Follow&style=social)](https://github.com/hi-manshu)
[![Twitter Follow](https://img.shields.io/twitter/follow/hi_man_shoe?label=Follow&style=social)](https://twitter.com/hi_man_shoe)
[![AndroidWeekly](https://img.shields.io/badge/Featured%20in%20androidweekly.net-%23532-orange)](https://androidweekly.net/issues/issue-532)
![GitHub issues](https://img.shields.io/github/issues/hi-manshu/charty)
![GitHub Repository size](https://img.shields.io/github/repo-size/hi-manshu/charty)
![GitHub forks](https://img.shields.io/github/forks/hi-manshu/charty)
![GitHub Repo stars](https://img.shields.io/github/stars/hi-manshu/charty)
![Charty](https://img.shields.io/maven-central/v/com.himanshoe/charty?color=f4c430&label=Maven%20Central%20%3A%20Charty)
![Charty Static Check](https://github.com/hi-manshu/charty/actions/workflows/static-check.yml/badge.svg)
## Implementation
### Gradle setup
In `build.gradle` of app module, include this dependency
```gradle
dependencies {
implementation("com.himanshoe:charty:{version}")
}
```
## Integrating it in your project
You can find the detail implementation of the following:
| Charts | Preview |
| ------------- |:-------------:|
| [BarChart](docs/BARCHART.md) | <img src="image/demo/barchart.png" width=50% height=50%> |
| [CandleStickChart](docs/CANDLECHART.md) | <img src="image/demo/candlestickchart.png" width=50% height=50%> |
| [BubbleChart](docs/BUBBLECHART.md) | <img src="image/demo/bubblechart.png" width=50% height=50%> |
| [StackedBarChart](docs/STACKEDBARCHART.md) | <img src="image/demo/stackedbarchart.png" width=50% height=50%> |
| [GroupedBarChart](docs/GROUPEDBARCHART.md) | <img src="image/demo/groupbarhchart.png" width=50% height=50%> |
| [CircleChart](docs/CIRCLECHART.md) | <img src="image/demo/circlechart.png" width=50% height=50%> |
| [PointChart](docs/POINTCHART.md) | <img src="image/demo/pointchart.png" width=50% height=50%> |
| [LineChart](docs/LINECHART.md) | <img src="image/demo/linechart.png" width=50% height=50%> |
| [CurveLineChart](docs/CURVEDLINECHART.md) | <img src="image/demo/curvedlinechart.png" width=50% height=50%> |
| [AreaChart](docs/AREACHART.md) | <img src="image/demo/areachart.png" width=50% height=50%> |
| [GaugeChart](docs/GAUGECHART.md) | <img src="image/demo/gaugechart.png" width=50% height=50%> |
| [PieChart](docs/PIECHART.md) | <img src="image/demo/Piechart.png" width=50% height=50%> |
### Contribution
Please feel free to fork it and open a PR.
## License
Copyright 2023 Charty Contributors
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
http://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.
+14
View File
@@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
Moving ahead, we will provide support exclusively for versions 2.x and above of Charty. The decision to focus on these versions stems from the significant performance improvements achieved through the complete API rewrite. By adopting the latest versions, you can benefit from enhanced performance and improved handling of APIs, resulting in a smoother and more efficient experience.
We recommend upgrading to version 2.x or higher to take advantage of these advancements and ensure optimal usage of Charty. Additionally, please refer to the updated documentation for guidance on utilizing the improved features and functionalities.
As we continue to evolve and refine Charty, our efforts will be concentrated on the latest versions to deliver the best possible performance and API handling. Thank you for your understanding and support in adopting the latest releases.
| Version | Supported |
| ------- | ------------------ |
| 2.0.0-alpha01 | :white_check_mark: |
| < 2.0.0-alpha01 | Old versioning |
+1
View File
@@ -0,0 +1 @@
/build
+66
View File
@@ -0,0 +1,66 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.himanshoe.chartysample'
compileSdk 33
defaultConfig {
applicationId "com.himanshoe.chartysample"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.7'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation(project(":charty"))
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.5.1'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,32 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.himanshoe.chartysample", appContext.packageName)
}
}
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Charty"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Charty">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,369 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.himanshoe.charty.area.AreaChart
import com.himanshoe.charty.area.model.AreaData
import com.himanshoe.charty.bar.BarChart
import com.himanshoe.charty.bar.model.BarData
import com.himanshoe.charty.bubble.BubbleChart
import com.himanshoe.charty.bubble.model.BubbleData
import com.himanshoe.charty.candle.CandleStickChart
import com.himanshoe.charty.candle.model.CandleData
import com.himanshoe.charty.circle.CircleChart
import com.himanshoe.charty.circle.model.CircleData
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ComposeList
import com.himanshoe.charty.common.toComposeList
import com.himanshoe.charty.gauge.GaugeChart
import com.himanshoe.charty.group.GroupedBarChart
import com.himanshoe.charty.group.model.GroupBarData
import com.himanshoe.charty.line.CurveLineChart
import com.himanshoe.charty.line.LineChart
import com.himanshoe.charty.line.model.LineData
import com.himanshoe.charty.pie.PieChart
import com.himanshoe.charty.pie.config.PieChartDefaults
import com.himanshoe.charty.pie.model.PieData
import com.himanshoe.charty.point.PointChart
import com.himanshoe.charty.radar.RadarChart
import com.himanshoe.charty.radar.model.RadarData
import com.himanshoe.charty.stacked.StackedBarChart
import com.himanshoe.charty.stacked.config.StackBarData
import kotlin.random.Random
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
setContent {
ChartContent(Modifier.fillMaxSize())
}
}
}
private fun generateMockBubbleData(count: Int = 10): List<BubbleData> {
val mockData = mutableListOf<BubbleData>()
for (i in 1..count) {
val xValue = "X$i"
val yValue = Random.nextFloat() * 100
val volumeSize = Random.nextFloat() * 100
val bubbleData = BubbleData(xValue, yValue, volumeSize)
mockData.add(bubbleData)
}
return mockData
}
private val data = listOf(
PieData(30f, "Category A", color = Color(0xffed625d)),
PieData(20f, "Category B", color = Color(0xfff79f88)),
PieData(13f, "Category C", color = Color(0xFF43A047)),
PieData(10f, "Category D", color = Color(0xFF93A047)),
)
private val circleData = listOf(
CircleData(30f, "Category A", color = Color(0xffed625d)),
CircleData(20f, "Category B", color = Color(0xfff79f88)),
CircleData(13f, "Category C", color = Color(0xFF43A047)),
CircleData(10f, "Category D", color = Color(0xFF93A047)),
)
private val bardata = listOf(
BarData(10f, "Category A", color = Color(0xffed625d)),
BarData(20f, "Category B", color = Color(0xffed125d)),
BarData(50f, "Category C", color = Color(0xffed225d)),
BarData(40f, "Category D", color = Color(0xffed325d)),
BarData(23f, "Category E", color = Color(0xffed425d)),
BarData(35F, "Category F", color = Color(0xffed525d)),
BarData(20f, "Category K", color = Color(0xffed615d)),
BarData(50f, "Category L", color = Color(0xffed625d)),
)
private val areaData = listOf(
AreaData(
points = listOf(0.5f, 0.8f, 0.6f, 0.9f, 0.7f, 0.4f),
xValue = "Item 1",
color = Color.Yellow
),
AreaData(
xValue = "Item 2",
points = listOf(0.33f, 0.6f, 0.93f, 0.7f, 0.9f, 1.5f),
color = Color.DarkGray
),
AreaData(
xValue = "Item 3",
points = listOf(0.3f, 0.6f, 0.4f, 0.7f, 0.9f, 0.3f),
color = Color.Red
),
)
@Composable
fun AreaChart() {
AreaChart(
modifier = Modifier
.size(400.dp),
areaData = ComposeList(
areaData
)
)
}
private val chartColors = listOf(
Color(0xffed625d),
Color(0xfff79f88),
Color(0xFF43A047)
)
private val groupData = listOf(
GroupBarData(
label = "Group 1",
dataPoints = listOf(10f, 15f, 8f),
chartColors
),
GroupBarData(
label = "Group 2",
dataPoints = listOf(12f, 6f, 14f),
chartColors
),
GroupBarData(
label = "Group 3",
dataPoints = listOf(5f, 9f, 11f),
chartColors
),
GroupBarData(
label = "Group 4",
dataPoints = listOf(5f, 9f, 11f),
chartColors
),
GroupBarData(
label = "Group 5",
dataPoints = listOf(12f, 6f, 14f),
chartColors
),
GroupBarData(
label = "Group 7",
dataPoints = listOf(5f, 9f, 11f),
chartColors
),
GroupBarData(
label = "Group 8",
dataPoints = listOf(1f, 9f, 11f),
chartColors
)
)
@Composable
fun StackedBarChartDemo() {
val stackBarData = ComposeList(
listOf(
StackBarData(dataPoints = listOf(3f, 20F, 30f), colors = chartColors, label = "1"),
StackBarData(dataPoints = listOf(9f, 25f, 35f), colors = chartColors, label = "2"),
StackBarData(dataPoints = listOf(10f, 25f, 35f), colors = chartColors, label = "3"),
StackBarData(dataPoints = listOf(1f, 25f, 3f), colors = chartColors, label = "4"),
StackBarData(dataPoints = listOf(10f, 25f, 5f), colors = chartColors, label = "5"),
StackBarData(dataPoints = listOf(10f, 25f, 35f), colors = chartColors, label = "6"),
StackBarData(dataPoints = listOf(10f, 25f, 35f), colors = chartColors, label = "7"),
StackBarData(dataPoints = listOf(10f, 25f, 35f), colors = chartColors, label = "8"),
StackBarData(dataPoints = listOf(10f, 25f, 20f), colors = chartColors, label = "9"),
StackBarData(
dataPoints = listOf(10f, 25f, 35f),
colors = chartColors,
label = "10"
),
StackBarData(
dataPoints = listOf(10f, 25f, 35f),
colors = chartColors,
label = "11"
),
StackBarData(
dataPoints = listOf(10f, 25f, 35f),
colors = chartColors,
label = "12"
),
StackBarData(
dataPoints = listOf(10f, 25f, 35f),
colors = chartColors,
label = "13"
),
StackBarData(
dataPoints = listOf(10f, 25f, 35f),
colors = chartColors,
label = "14"
),
)
)
StackedBarChart(
stackBarData = stackBarData,
modifier = Modifier
.fillMaxWidth()
.height(400.dp),
)
}
@Composable
fun CandlestickChartExample() {
val candleData = listOf(
CandleData(high = 20f, low = 8f, open = 10f, close = 15f),
CandleData(high = 22f, low = 16f, open = 18f, close = 20f),
CandleData(high = 14f, low = 8f, open = 12f, close = 9f),
CandleData(high = 9f, low = 3f, open = 7f, close = 5f),
CandleData(high = 10f, low = 4f, open = 6f, close = 8f),
CandleData(high = 15f, low = 10f, open = 13f, close = 12f),
CandleData(high = 20f, low = 8f, open = 10f, close = 15f),
CandleData(high = 22f, low = 16f, open = 18f, close = 20f),
CandleData(high = 14f, low = 8f, open = 12f, close = 9f),
CandleData(high = 9f, low = 3f, open = 7f, close = 5f),
CandleData(high = 10f, low = 4f, open = 6f, close = 8f),
CandleData(high = 15f, low = 10f, open = 13f, close = 12f),
CandleData(high = 9f, low = 3f, open = 7f, close = 5f),
CandleData(high = 10f, low = 4f, open = 6f, close = 8f),
CandleData(high = 15f, low = 10f, open = 13f, close = 12f),
CandleData(high = 20f, low = 8f, open = 10f, close = 15f),
CandleData(high = 22f, low = 16f, open = 18f, close = 20f),
CandleData(high = 14f, low = 8f, open = 12f, close = 9f),
CandleData(high = 9f, low = 3f, open = 7f, close = 5f),
CandleData(high = 10f, low = 4f, open = 6f, close = 8f),
CandleData(high = 15f, low = 10f, open = 13f, close = 12f),
)
CandleStickChart(
candleData = ComposeList(candleData),
modifier = Modifier
.fillMaxWidth()
.height(400.dp),
)
}
@Composable
private fun ChartContent(modifier: Modifier = Modifier) {
LazyColumn(modifier) {
item {
BarChart(
dataCollection = ChartDataCollection(bardata),
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
)
}
item {
val percentValue = 75
GaugeChart(percentValue = percentValue)
}
item {
StackedBarChartDemo()
}
item {
CandlestickChartExample()
}
item {
GroupedBarChart(
groupBarDataCollection = groupData.toComposeList(),
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
)
}
item {
AreaChart()
}
item {
BubbleChart(
modifier = Modifier
.fillMaxWidth()
.height(400.dp),
dataCollection = ChartDataCollection(generateMockBubbleData())
)
}
item {
CircleChart(
modifier = Modifier.size(400.dp),
dataCollection = ChartDataCollection(circleData),
)
}
item {
PieChart(
pieChartConfig = PieChartDefaults.defaultConfig().copy(donut = false),
dataCollection = ChartDataCollection(data),
modifier = Modifier.wrapContentSize()
)
}
item {
PieChart(
dataCollection = ChartDataCollection(data),
modifier = Modifier.wrapContentSize()
)
}
item {
RadarChart(
dataCollection = ChartDataCollection(generateMockRadarDataList()),
modifier = Modifier.size(400.dp),
)
}
item {
CurveLineChart(
dataCollection = ChartDataCollection(generateMockLineDataList()),
modifier = Modifier
.size(450.dp),
)
}
item {
LineChart(
dataCollection = ChartDataCollection(generateMockLineDataList()),
modifier = Modifier
.size(450.dp),
)
}
item {
PointChart(
dataCollection = ChartDataCollection(generateMockLineDataList()),
modifier = Modifier
.size(450.dp),
)
}
}
}
private fun generateMockLineDataList(): List<LineData> {
return listOf(
LineData(0F, "Jan"),
LineData(10F, "Feb"),
LineData(05F, "Mar"),
LineData(50F, "Apr"),
LineData(55F, "May"),
LineData(03F, "June"),
LineData(9F, "July"),
LineData(40F, "Aug"),
LineData(60F, "Sept"),
LineData(33F, "Oct"),
LineData(11F, "Nov"),
)
}
private fun generateMockRadarDataList(): List<RadarData> {
return listOf(
RadarData(0F, "Jan"),
RadarData(10F, "Feb"),
RadarData(05F, "Mar"),
RadarData(50F, "Apr"),
RadarData(55F, "May"),
)
}
}
@@ -0,0 +1,19 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@@ -0,0 +1,78 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun ChartyTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
@@ -0,0 +1,42 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample.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,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)
@@ -0,0 +1,38 @@
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
@@ -0,0 +1,11 @@
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<resources>
<string name="app_name">Charty</string>
</resources>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<resources>
<style name="Theme.Charty" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
@@ -0,0 +1,25 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.chartysample
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+14
View File
@@ -0,0 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.0"
classpath "org.jlleitschuh.gradle:ktlint-gradle:11.3.2"
}
}
plugins {
id 'com.android.application' version '8.0.0' apply false
id 'com.android.library' version '8.0.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.21' apply false
id("org.jetbrains.dokka") version "1.8.20"
}
+1
View File
@@ -0,0 +1 @@
/build
+76
View File
@@ -0,0 +1,76 @@
import com.vanniktech.maven.publish.SonatypeHost
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.dokka'
id 'com.vanniktech.maven.publish' version "0.25.3"
}
apply from: '../quality/static-check.gradle'
android {
namespace 'com.himanshoe.charty'
compileSdk 33
defaultConfig {
minSdk 24
targetSdk 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.7'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.4.7'
}
}
mavenPublishing {
publishToMavenCentral(SonatypeHost.S01)
signAllPublications()
}
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
//Compose
implementation platform('androidx.compose:compose-bom:2023.05.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
debugImplementation "androidx.compose.ui:ui-tooling"
implementation 'androidx.compose.ui:ui-util'
implementation 'androidx.compose.material:material-icons-extended'
//time
implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.4.0'
lintChecks('com.slack.lint.compose:compose-lint-checks:1.2.0')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
dokkaPlugin("org.jetbrains.dokka:android-documentation-plugin:1.8.20")
}
View File
+24
View File
@@ -0,0 +1,24 @@
#
# /***************
# * Charty Library : Android
# *
# * Copyright (c) 2023. Charty Contributor
# ****************/
#
# Maven
POM_ARTIFACT_ID=charty
POM_NAME=charty
POM_DESCRIPTION=An Elementary Compose Chart library.
POM_PACKAGING=aar
POM_INCEPTION_YEAR=2023
GROUP=com.himanshoe
VERSION_NAME=2.0.0-alpha01
VERSION_CODE=2
POM_URL=https://github.com/hi-manshu
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_SCM_URL=https://github.com/hi-manshu
POM_DEVELOPER_ID=hi-manshu
POM_DEVELOPER_NAME=Himanshu Singh
POM_DEVELOPER_URL=https://github.com/hi-manshu
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /***************
~ * Charty Library : Android
~ *
~ * Copyright (c) 2023. Charty Contributor
~ ****************/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,115 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.area
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.himanshoe.charty.area.model.AreaData
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.ComposeList
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.toComposeList
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawYAxis
/**
* A composable function that displays an area chart based on the provided area data.
*
* @param areaData The collection of area data containing labels and points that define the areas on the chart.
* @param modifier Modifier for applying styling and positioning to the chart.
* @param axisConfig Configuration for the chart axes, including color and stroke width.
* @param padding Padding around the chart content to create spacing.
*/
@Composable
fun AreaChart(
areaData: ComposeList<AreaData>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
padding: Dp = 16.dp,
) {
val items = areaData.data.flatMap { it.points }
val maxValue = items.maxOrNull() ?: 0F
val minValue = items.minOrNull() ?: 0f
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
ChartSurface(
modifier = modifier.fillMaxSize(),
padding = padding,
chartData = items.toComposeList(),
content = {
Column {
Canvas(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val width = size.width
val height = size.height
val xStep = width / (areaData.data[0].points.size - 1)
val yStep = height / (maxValue - minValue)
areaData.data.fastForEach { data ->
val dataPoints = data.points
val dataPointsPath = Path()
dataPointsPath.moveTo(0f, height - ((dataPoints[0] - minValue) * yStep))
for (i in 1 until dataPoints.size) {
val x = i * xStep
val y = height - ((dataPoints[i] - minValue) * yStep)
dataPointsPath.lineTo(x, y)
}
dataPointsPath.lineTo(width, height)
dataPointsPath.lineTo(0f, height)
drawPath(
path = dataPointsPath,
color = data.color,
style = Fill,
)
}
}
}
}
)
}
@@ -0,0 +1,26 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.area.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Data class representing an area on the AreaChart.
*
* @param xValue The x-value of the area. It can be of any type (e.g., String, Int, Float, etc.).
* @param points The list of y-values that define the area's shape. Each y-value corresponds to a specific data point.
* @param color The color of the area.
*/
@Immutable
data class AreaData(
val xValue: Any,
val points: List<Float>,
val color: Color
)
@@ -0,0 +1,232 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.bar
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.bar.model.BarData
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxis
/**
* A composable function that displays a bar chart based on the provided data collection.
*
* @param dataCollection The collection of chart data representing the bars in the chart.
* @param modifier Modifier for applying styling and positioning to the chart.
* @param barSpacing The spacing between bars in the chart.
* @param padding Padding around the chart content to create spacing.
* @param barColor The color used to fill the bars in the chart.
* @param axisConfig Configuration for the chart axes, including color and stroke width.
*/
@Composable
fun BarChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
barSpacing: Dp = 8.dp,
padding: Dp = 16.dp,
barColor: Color = Color.Blue,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
) {
val points = dataCollection.data
val screenWidth = with(LocalContext.current) { resources.displayMetrics.widthPixels.toFloat() }
var chartBound by remember { mutableStateOf(0F) }
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val data = dataCollection.data
androidx.compose.foundation.Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
chartBound = chartWidth.div(
points
.count()
.times(1.2F)
)
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val maxValue = dataCollection.maxYValue()
val barCount = data.size
val minValue = dataCollection.minYValue()
val range = maxValue - minValue
val availableWidth = chartWidth - (barSpacing.toPx() * (barCount - 1))
val maxBarWidth = availableWidth / barCount
data.fastForEachIndexed { index, barData ->
val barWidth = maxBarWidth.coerceAtMost(screenWidth / barCount)
val barHeight =
(barData.yValue - minValue) / range * (chartHeight - axisConfig.axisStroke)
val startX = index * (barWidth + barSpacing.toPx())
val startY = chartHeight - barHeight
if (barData is BarData) {
drawRect(
color = barColor,
topLeft = Offset(startX, startY),
size = Size(barWidth, barHeight)
)
if (data.count() < 14) {
drawXAxisLabels(
data = barData.xValue,
center = Offset(startX + barWidth / 2, startY),
count = data.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
} else {
throw NoSuchFieldException("$barData should have color")
}
}
}
}
}
/**
* A composable function that displays a bar chart based on the provided data collection.
*
* @param dataCollection The collection of chart data representing the bars in the chart.
* @param modifier Modifier for applying styling and positioning to the chart.
* @param barSpacing The spacing between bars in the chart.
* @param padding Padding around the chart content to create spacing.
* @param axisConfig Configuration for the chart axes, including color and stroke width.
*/
@Composable
fun BarChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
barSpacing: Dp = 8.dp,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
) {
val points = dataCollection.data
val screenWidth = with(LocalContext.current) { resources.displayMetrics.widthPixels.toFloat() }
var chartBound by remember { mutableStateOf(0F) }
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val data = dataCollection.data
androidx.compose.foundation.Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
chartBound = chartWidth.div(
points
.count()
.times(1.2F)
)
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val maxValue = dataCollection.maxYValue()
val barCount = data.size
val minValue = if (dataCollection.minYValue() < 0) {
dataCollection.minYValue()
} else {
0F
}
val range = maxValue - minValue
val availableWidth = chartWidth - (barSpacing.toPx() * (barCount - 1))
val maxBarWidth = availableWidth / barCount
data.fastForEachIndexed { index, barData ->
val barWidth = maxBarWidth.coerceAtMost(screenWidth / barCount)
val barHeight =
(barData.yValue - minValue) / range * (chartHeight - axisConfig.axisStroke)
val startX = index * (barWidth + barSpacing.toPx())
val startY = chartHeight - barHeight
if (barData is BarData) {
if (barData.color != null) {
drawRect(
color = barData.color,
topLeft = Offset(startX, startY),
size = Size(barWidth, barHeight)
)
} else {
throw NoSuchFieldException("$barData should have color")
}
if (data.count() < 14) {
drawXAxisLabels(
data = barData.xValue,
center = Offset(startX + barWidth / 2, startY),
count = data.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
}
}
}
}
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.bar.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.himanshoe.charty.common.ChartData
/**
* Data class representing a bar in a bar chart.
*
* @param yValue The value of the bar on the y-axis.
* @param xValue The value of the bar on the x-axis. It can be of any type (e.g., String, Int, Float, etc.).
* @param color The color of the bar. If not specified, the default color will be used.
*/
@Immutable
data class BarData(override val yValue: Float, override val xValue: Any, val color: Color? = null) :
ChartData {
override val chartString: String
get() = "Bar Chart"
}
@@ -0,0 +1,128 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.bubble
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.bubble.model.BubbleData
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.line.config.CurvedLineChartColors
import com.himanshoe.charty.line.config.CurvedLineChartDefaults
/**
* A composable function that displays a bubble chart based on the provided data collection.
*
* @param dataCollection The collection of chart data representing the bubbles in the chart.
* @param modifier Modifier for applying styling and positioning to the chart.
* @param padding Padding around the chart content to create spacing.
* @param axisConfig Configuration for the chart axes, including color and stroke width.
* @param chartColors Configuration for the colors used in the bubble chart.
*/
@Composable
fun BubbleChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
chartColors: CurvedLineChartColors = CurvedLineChartDefaults.defaultColor(),
) {
val points = dataCollection.data
var chartWidth by remember { mutableStateOf(0f) }
var chartHeight by remember { mutableStateOf(0f) }
val horizontalScale = chartWidth.div(points.count())
val verticalScale = chartHeight.div((dataCollection.maxYValue() - dataCollection.minYValue()))
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val contentColor = Brush.linearGradient(chartColors.contentColor)
val backgroundColor = Brush.linearGradient(chartColors.backgroundColors)
val data = dataCollection.data.filterIsInstance<BubbleData>()
val minYValue = dataCollection.minYValue()
Canvas(
modifier = Modifier
.background(backgroundColor)
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val maxVolumeSize = data.maxOf { it.volumeSize }
dataCollection.data.fastForEachIndexed { index, data ->
if (data is BubbleData) {
val bubbleRadius =
data.volumeSize / maxVolumeSize * 50 // Adjust the scaling factor here
val x = index.toFloat() * horizontalScale + bubbleRadius
val y =
(chartHeight - ((data.yValue - minYValue) * verticalScale) - bubbleRadius)
.coerceIn(0f, chartHeight - bubbleRadius * 2) + bubbleRadius
drawCircle(
color = Color.Blue,
radius = bubbleRadius,
center = Offset(x, y)
)
if (points.count() < 14) {
drawXAxisLabels(
data = data.xValue,
center = Offset(x, y),
count = points.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
} else {
throw ClassCastException("Use ChartDataCollection for BubbleData")
}
}
}
}
}
@@ -0,0 +1,29 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.bubble.model
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.ChartData
/**
* Data class representing a bubble in a bubble chart.
*
* @param xValue The value of the bubble on the x-axis. It can be of any type (e.g., String, Int, Float, etc.).
* @param yValue The value of the bubble on the y-axis.
* @param volumeSize The size of the bubble, typically representing a third variable.
*/
@Immutable
data class BubbleData(
override val xValue: Any,
override val yValue: Float,
val volumeSize: Float,
) : ChartData {
override val chartString: String
get() = "Bubble Chart"
}
@@ -0,0 +1,170 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.candle
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.candle.config.CandleStickConfig
import com.himanshoe.charty.candle.config.CandleStickDefaults
import com.himanshoe.charty.candle.model.CandleData
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.ComposeList
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.toComposeList
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawYAxis
/**
* Renders a candlestick chart based on the provided candle data.
*
* @param candleData The list of candlestick data.
* @param modifier The modifier for the chart.
* @param axisConfig The configuration for the chart axes.
* @param padding The padding around the chart.
* @param candleConfig The configuration for the candlesticks.
*/
@Composable
fun CandleStickChart(
candleData: ComposeList<CandleData>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
padding: Dp = 16.dp,
candleConfig: CandleStickConfig = CandleStickDefaults.defaultCandleStickConfig(),
) {
val listOfAxisValues = candleData.data.flatMap { listOf(it.high, it.low, it.open, it.close) }
.distinct()
.sorted()
val maxValue = candleData.data.maxOf { maxOf(it.high, it.open, it.close) }
val minValue = candleData.data.minOf { minOf(it.low, it.open, it.close) }
var chartWidth by remember { mutableStateOf(0f) }
var chartHeight by remember { mutableStateOf(0f) }
ChartSurface(
modifier = modifier,
padding = padding,
axisConfig = axisConfig,
chartData = listOfAxisValues.toComposeList(),
content = {
Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val innerWidth = chartWidth - padding.toPx()
val innerHeight = chartHeight - padding.toPx() * 1.1F
val candleCount = candleData.data.size
val candleWidth = if (candleCount < 15) {
innerWidth / 15
} else {
innerWidth / candleCount
}
candleData.data.fastForEachIndexed { index, data ->
val x = padding.toPx() + index * candleWidth
val yHigh = calculateYPosition(
data.high,
maxValue,
minValue,
innerHeight
) + padding.toPx()
val yLow = calculateYPosition(
data.low,
maxValue,
minValue,
innerHeight
) + padding.toPx()
val yOpen = calculateYPosition(
data.open,
maxValue,
minValue,
innerHeight
) + padding.toPx()
val yClose = calculateYPosition(
data.close,
maxValue,
minValue,
innerHeight
) + padding.toPx()
val wickStartX = x + candleWidth.div(2)
val wickEndX = x + candleWidth.div(2)
// Draw wick
drawLine(
color = candleConfig.wickColor,
start = Offset(wickStartX, yHigh),
end = Offset(wickEndX, yLow),
strokeWidth = calculateWickStrokeWidth(
candleWidth,
candleConfig.wickWidthScale
)
)
// Draw body
val bodyStartY = minOf(yOpen, yClose)
val bodyEndY = maxOf(yOpen, yClose)
val bodyWidth = calculateBodyWidth(candleWidth)
drawRect(
color = if (data.close >= data.open) candleConfig.positiveColor else candleConfig.negativeColor,
topLeft = Offset(x, bodyStartY),
size = Size(bodyWidth, bodyEndY - bodyStartY)
)
}
}
}
)
}
private fun calculateWickStrokeWidth(candleWidth: Float, wickWidthScale: Float) =
candleWidth * wickWidthScale
private fun calculateBodyWidth(candleWidth: Float) = candleWidth * 0.8f
private fun calculateYPosition(
value: Float,
maxValue: Float,
minValue: Float,
chartHeight: Float
): Float {
return chartHeight * (1 - (value - minValue) / (maxValue - minValue))
}
@@ -0,0 +1,30 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.candle.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Configuration options for a candlestick chart.
*
* @param positiveColor The color of positive (rising) candles.
* @param negativeColor The color of negative (falling) candles.
* @param wickColor The color of the wicks (lines) connecting the candle bodies.
* @param canCandleScale Whether the candles can be scaled.
* @param wickWidthScale The scaling factor for the width of the wicks, relative to the candle width.
*/
@Immutable
data class CandleStickConfig(
val positiveColor: Color,
val negativeColor: Color,
val wickColor: Color,
val canCandleScale: Boolean,
val wickWidthScale: Float = 0.05f,
)
@@ -0,0 +1,28 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.candle.config
import androidx.compose.ui.graphics.Color
/**
* Default configuration options for a candlestick chart.
*/
object CandleStickDefaults {
/**
* Returns the default configuration for a candlestick chart.
*
* @return The default CandleStickConfig.
*/
fun defaultCandleStickConfig() = CandleStickConfig(
Color.Green,
Color.Red,
Color.Black,
false
)
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.candle.model
import androidx.compose.runtime.Immutable
/**
* Data class representing a single candlestick in a candlestick chart.
*
* @param high The highest value of the candlestick.
* @param low The lowest value of the candlestick.
* @param open The opening value of the candlestick.
* @param close The closing value of the candlestick.
*/
@Immutable
data class CandleData(
val high: Float,
val low: Float,
val open: Float,
val close: Float
)
@@ -0,0 +1,173 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.circle
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import androidx.compose.ui.util.fastMap
import com.himanshoe.charty.circle.config.CircleChartConfig
import com.himanshoe.charty.circle.config.CircleChartLabelTextConfig
import com.himanshoe.charty.circle.config.CircleConfigDefaults
import com.himanshoe.charty.circle.model.CircleData
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.toChartDataCollection
/**
* Renders a circle chart based on the provided data.
*
* @param dataCollection The collection of chart data.
* @param modifier The modifier to apply to the chart.
* @param canAnimate Whether the chart should animate its appearance.
* @param textLabelTextConfig The configuration for the label text in the chart.
* @param config The configuration for the circle chart.
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun CircleChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
canAnimate: Boolean = true,
textLabelTextConfig: CircleChartLabelTextConfig = CircleConfigDefaults.defaultTextLabelConfig(),
config: CircleChartConfig = CircleConfigDefaults.circleChartConfig(),
) {
val maxYValueState = rememberSaveable { mutableStateOf(dataCollection.maxYValue()) }
val maxYValue = maxYValueState.value
val angleFactor = if (config.maxValue != null) 360.div(config.maxValue) else 360.div(maxYValue)
val animatedFactor = remember {
Animatable(initialValue = 0f)
}
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
LaunchedEffect(Unit) {
animatedFactor.animateTo(
targetValue = angleFactor,
animationSpec = tween(1000)
)
}
Canvas(
modifier = modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
) {
val scaleFactor = chartWidth.div(dataCollection.data.count())
val sizeArc = size.div(scaleFactor)
val factor = if (canAnimate) animatedFactor.value else angleFactor
dataCollection.data.fastForEachIndexed { index, circleData ->
if (circleData is CircleData) {
val arcWidth = sizeArc.width.plus(index.times(scaleFactor))
val arcHeight = sizeArc.height.plus(index.times(scaleFactor))
drawArc(
color = circleData.color,
startAngle = config.startAngle.angle,
sweepAngle = factor.times(circleData.yValue),
topLeft = Offset(
(chartWidth - arcWidth).div(2f),
(chartHeight - arcHeight).div(2f)
),
useCenter = false,
style = Stroke(width = scaleFactor.div(2.5F), cap = StrokeCap.Round),
size = Size(arcWidth, arcHeight)
)
}
}
}
if (config.showLabel) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 4.dp),
horizontalArrangement = Arrangement.Center
) {
dataCollection.data.fastMap {
if (it is CircleData) {
Box(
Modifier
.size(textLabelTextConfig.indicatorSize)
.clip(CircleShape)
.align(Alignment.CenterVertically)
.background(it.color)
)
Text(
text = it.xValue.toString(),
fontSize = textLabelTextConfig.textSize,
fontStyle = textLabelTextConfig.fontStyle,
fontWeight = textLabelTextConfig.fontWeight,
fontFamily = textLabelTextConfig.fontFamily,
maxLines = textLabelTextConfig.maxLine,
overflow = textLabelTextConfig.overflow,
modifier = Modifier.padding(
end = 8.dp,
start = 4.dp
)
)
}
}
}
}
}
@Preview
@Composable
private fun CircleChartScreen() {
val circleData = listOf(
CircleData(10F, 235F, color = Color(0xFFfafa6e)),
CircleData(10F, 135F, color = Color(0xFFc4ec74)),
CircleData(10F, 315F, color = Color(0xFF92dc7e)),
CircleData(20F, 50F, color = Color(0xFF64c987)),
CircleData(30F, 315F, color = Color(0xFF39b48e))
)
CircleChart(
modifier = Modifier
.scale(1f)
.size(400.dp)
.padding(20.dp),
dataCollection = circleData.toChartDataCollection(),
)
}
@@ -0,0 +1,26 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.circle.config
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.config.StartAngle
/**
* Configuration options for a circle chart.
*
* @param startAngle The start angle of the chart.
* @param maxValue The maximum value of the chart.
* @param showLabel Whether to show labels on the chart.
*/
@Immutable
data class CircleChartConfig(
val startAngle: StartAngle = StartAngle.Zero,
val maxValue: Float?,
val showLabel: Boolean
)
@@ -0,0 +1,40 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.circle.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
/**
* Configuration options for the labels in a circle chart.
*
* @param textSize The size of the text.
* @param fontStyle The style of the font.
* @param fontWeight The weight of the font.
* @param fontFamily The family of the font.
* @param indicatorSize The size of the label indicator.
* @param maxLine The maximum number of lines for the label text.
* @param overflow The text overflow behavior.
*/
@Immutable
data class CircleChartLabelTextConfig(
val textSize: TextUnit,
val fontStyle: FontStyle? = null,
val fontWeight: FontWeight? = null,
val fontFamily: FontFamily? = null,
val indicatorSize: Dp = 10.dp,
val maxLine: Int = 1,
val overflow: TextOverflow = TextOverflow.Ellipsis
)
@@ -0,0 +1,39 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.circle.config
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import com.himanshoe.charty.common.config.StartAngle
/**
* Provides default configuration values for a circle chart and its labels.
*/
object CircleConfigDefaults {
/**
* Returns the default configuration for a circle chart.
*/
fun circleChartConfig() = CircleChartConfig(
startAngle = StartAngle.Zero,
maxValue = null,
showLabel = true
)
/**
* Returns the default configuration for the labels in a circle chart.
*/
fun defaultTextLabelConfig() = CircleChartLabelTextConfig(
textSize = TextUnit.Unspecified,
fontStyle = null,
fontWeight = null,
fontFamily = null,
maxLine = 1,
overflow = TextOverflow.Ellipsis
)
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.circle.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.himanshoe.charty.common.ChartData
/**
* Represents the data for a circle in a chart.
*
* @param yValue The y-value of the circle.
* @param xValue The x-value of the circle.
* @param color The color of the circle.
*/
@Immutable
data class CircleData(override val yValue: Float, override val xValue: Any, val color: Color) :
ChartData {
override val chartString: String
get() = "Circle Chart"
}
@@ -0,0 +1,100 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.util.fastMap
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxisLabels
@Composable
fun ChartSurface(
padding: Dp,
chartData: ChartDataCollection,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
content: @Composable () -> Unit = {}
) {
BoxWithConstraints(
modifier = modifier
.padding(
start = padding.times(2),
bottom = padding.times(2),
top = padding.times(2),
end = padding
)
.drawBehind {
if (chartData.data.count() >= 14 && axisConfig.showGridLabel) {
drawXAxisLabels(
data = chartData.data.fastMap { it.xValue },
count = chartData.data.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount
)
}
if (axisConfig.showGridLabel) {
val newItems = if (chartData.minYValue() > 0) {
listOf(0F) + chartData.data.map { it.yValue }
} else {
chartData.data.map { it.yValue }
}
drawYAxisLabels(
newItems,
spacing = padding.toPx(),
)
}
}
.background(Color.White),
contentAlignment = Alignment.Center
) {
content()
}
}
@Composable
fun <T> ChartSurface(
padding: Dp,
chartData: ComposeList<T>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
content: @Composable () -> Unit = {}
) {
BoxWithConstraints(
modifier = modifier
.padding(
start = padding.times(2),
bottom = padding.times(2),
top = padding.times(2),
end = padding
)
.drawBehind {
if (axisConfig.showGridLabel) {
drawYAxisLabels(
chartData.data as List<Float>,
spacing = padding.toPx(),
)
}
}
.background(Color.White),
contentAlignment = Alignment.Center
) {
content()
}
}
@@ -0,0 +1,57 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common
import androidx.compose.runtime.Immutable
/*
* Represents a single data point in a chart.
* - xValue: The value on the x-axis.
* - yValue: The value on the y-axis.
* - chartString: A string representation of the data point.
*/
interface ChartData {
val xValue: Any
val yValue: Float
val chartString: String
}
/*
* Represents a collection of ChartData objects.
* - data: The list of ChartData objects.
*/
@Immutable
data class ChartDataCollection(val data: List<ChartData>)
/*
* Extension function to convert a List of ChartData objects to a ChartDataCollection.
*/
fun List<ChartData>.toChartDataCollection() = ChartDataCollection(this)
/*
* Returns the maximum y-value among all the data points in the ChartDataCollection.
*/
fun ChartDataCollection.maxYValue() = data.maxOf { it.yValue }
/*
* Returns the minimum y-value among all the data points in the ChartDataCollection.
*/
fun ChartDataCollection.minYValue() = data.minOf { it.yValue }
/*
* Represents a collection of items for Compose.
* - data: The list of items.
*/
@Immutable
data class ComposeList<T>(val data: List<T>)
/*
* Extension function to convert a List to a ComposeList.
*/
fun <T> List<T>.toComposeList() = ComposeList(this)
@@ -0,0 +1,34 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Configuration for the axes and grid lines in a chart.
*
* @param showAxes Whether to show the axes.
* @param showGridLines Whether to show the grid lines.
* @param showGridLabel Whether to show the grid labels.
* @param axisStroke The stroke width of the axes.
* @param minLabelCount The minimum number of labels to display.
* @param axisColor The color of the axes.
* @param gridColor The color of the grid lines. Defaults to a slightly transparent version of the axis color.
*/
@Immutable
data class AxisConfig(
val showAxes: Boolean,
val showGridLines: Boolean,
val showGridLabel: Boolean,
val axisStroke: Float,
val minLabelCount: Int,
val axisColor: Color,
val gridColor: Color = axisColor.copy(alpha = 0.5F),
)
@@ -0,0 +1,24 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Colors used in a chart.
*
* @param contentColor The color(s) for the content of the chart.
* @param backgroundColors The background color(s) of the chart.
*/
@Immutable
data class ChartColors(
val contentColor: List<Color> = emptyList(),
val backgroundColors: List<Color> = emptyList(),
)
@@ -0,0 +1,54 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.config
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
object ChartDefaults {
/**
* Provides default values for the axis configuration.
*/
fun axisConfigDefaults() = AxisConfig(
showGridLabel = true,
showAxes = true,
showGridLines = true,
axisStroke = 2F,
axisColor = Color.Black,
minLabelCount = 2
)
/**
* Provides default colors for the chart.
*/
fun colorDefaults() = ChartColors(
contentColor = listOf(
Color(0xffed625d),
Color(0xfff79f88)
),
backgroundColors = listOf(
Color.Transparent, Color.Transparent
)
)
/**
* Provides default values for text label configuration in the chart.
*/
fun defaultTextLabelConfig() = ChartyLabelTextConfig(
textSize = TextUnit.Unspecified,
fontStyle = null,
fontWeight = null,
fontFamily = null,
textColor = Color.Black,
maxLine = 1,
overflow = TextOverflow.Ellipsis
)
}
@@ -0,0 +1,43 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
/**
* Represents the configuration for the text labels in a chart.
*
* @property textSize The size of the text labels.
* @property textColor The color of the text labels.
* @property fontStyle The style of the font (e.g., italic). Defaults to null.
* @property fontWeight The weight of the font (e.g., bold). Defaults to null.
* @property fontFamily The family of the font (e.g., serif). Defaults to null.
* @property indicatorSize The size of the indicator associated with the labels. Defaults to 10.dp.
* @property maxLine The maximum number of lines the label can span. Defaults to 1.
* @property overflow The behavior when the text exceeds the available space (e.g., ellipsis). Defaults to TextOverflow.Ellipsis.
*/
@Immutable
data class ChartyLabelTextConfig(
val textSize: TextUnit,
val textColor: Color,
val fontStyle: FontStyle? = null,
val fontWeight: FontWeight? = null,
val fontFamily: FontFamily? = null,
val indicatorSize: Dp = 10.dp,
val maxLine: Int = 1,
val overflow: TextOverflow = TextOverflow.Ellipsis
)
@@ -0,0 +1,44 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.config
import androidx.compose.runtime.Immutable
/**
* Represents the start angle for a circular chart.
*/
@Immutable
sealed class StartAngle(open val angle: Float) {
/**
* Start angle of 0 degrees.
*/
object Zero : StartAngle(0F)
/**
* Start angle of 180 degrees (straight angle).
*/
object StraightAngle : StartAngle(180F)
/**
* Start angle of 270 degrees (reflex angle).
*/
object ReflexAngle : StartAngle(270F)
/**
* Start angle of 90 degrees (right angle).
*/
object RightAngle : StartAngle(90F)
/**
* Custom start angle specified by the given angle in degrees.
*
* @param angle The custom start angle in degrees.
*/
data class CustomAngle(override val angle: Float) : StartAngle(angle)
}
@@ -0,0 +1,35 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.math
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
/**
* Calculates the offset for a data point on a chart.
*
* @param index The index of the data point.
* @param bound The width of each data point.
* @param size The size of the chart.
* @param data The value of the data point.
* @param scaleFactor The scale factor for the data points.
* @return The offset of the data point.
*/
internal fun chartDataToOffset(
index: Int,
bound: Float,
size: Size,
data: Float,
scaleFactor: Float
): Offset {
val startX = index * bound * 1.2F
val endX = (index + 1) * bound * 1.2F
val y = size.height - data * scaleFactor
return Offset((startX + endX) / 2F, y)
}
@@ -0,0 +1,43 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.ui
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
/**
* Draws the X axis.
*
* @param color The color of the axis.
* @param stroke The stroke width of the axis.
*/
fun DrawScope.drawXAxis(color: Color, stroke: Float) {
drawLine(
start = Offset(0f, 0F),
end = Offset(0f, size.height),
color = color,
strokeWidth = stroke,
)
}
/**
* Draws the Y axis.
*
* @param color The color of the axis.
* @param stroke The stroke width of the axis.
*/
fun DrawScope.drawYAxis(color: Color, stroke: Float) {
drawLine(
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
color = color,
strokeWidth = stroke
)
}
@@ -0,0 +1,151 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.ui
import android.graphics.Paint
import android.graphics.Rect
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
/**
* Draws X-axis labels for a single data point.
*
* @param data The data value for the label.
* @param center The center position of the label.
* @param count The total count of data points.
* @param padding The padding value.
* @param minLabelCount The minimum label count.
* @param textColor The color of the text label.
*/
internal fun DrawScope.drawXAxisLabels(
data: Any,
center: Offset,
count: Int,
padding: Float,
minLabelCount: Int,
textColor: Color = Color.Black,
) {
val divisibleFactor = if (count > 10) count else 1
val textSizeFactor = if (count > 10) 3 else 30
val textBounds = Rect()
val textPaint = Paint().apply {
color = textColor.toArgb()
textSize = size.width / textSizeFactor / divisibleFactor
textAlign = Paint.Align.CENTER
getTextBounds(data.toString(), 0, data.toString().length, textBounds)
}
drawContext.canvas.nativeCanvas.drawText(
data.toString().take(minLabelCount),
center.x,
size.height + padding,
textPaint
)
}
/**
* Draws X-axis labels for a list of data points.
*
* @param data The list of data points.
* @param count The total count of data points.
* @param padding The padding value.
* @param minLabelCount The minimum label count.
* @param textColor The color of the text labels.
*/
internal fun DrawScope.drawXAxisLabels(
data: List<Any>,
count: Int,
padding: Float,
minLabelCount: Int,
textColor: Color = Color.Black,
) {
val textBounds = Rect()
val textPaint = Paint().apply {
color = textColor.toArgb()
textSize = size.width / 30
textAlign = Paint.Align.CENTER
getTextBounds(data.toString(), 0, data.toString().length, textBounds)
}
val xStart = padding / 2
val xEnd = size.width - padding
val nearestCenterIndex = if (count % 2 == 0) count / 2 else (count - 1) / 2
val xMiddle = nearestCenterIndex * (size.width - padding) / (count - 1)
val y = size.height + padding
val textCount = data.toString().length.coerceAtLeast(minLabelCount)
drawContext.canvas.nativeCanvas.drawText(
data.first().toString().take(textCount),
xStart,
y,
textPaint
)
drawContext.canvas.nativeCanvas.drawText(
data.last().toString().take(textCount),
xEnd,
y,
textPaint
)
drawContext.canvas.nativeCanvas.drawText(
data[nearestCenterIndex].toString().take(textCount),
xMiddle,
y,
textPaint
)
}
/**
* Draws Y-axis labels for a list of values.
*
* @param values The list of values.
* @param spacing The spacing between labels.
* @param textColor The color of the text labels.
*/
fun DrawScope.drawYAxisLabels(
values: List<Float>,
spacing: Float,
textColor: Color = Color.Black,
) {
val maxLabelCount = 4
val maxLabelValue = values.maxOrNull() ?: return
val minLabelValue = values.minOrNull() ?: return
val labelRange = maxLabelValue - minLabelValue
val textPaint = Paint().apply {
color = textColor.toArgb()
textSize = size.width / 30
textAlign = Paint.Align.CENTER
}
val labelSpacing = size.height / (maxLabelCount - 1)
repeat(maxLabelCount) { i ->
val y = size.height - (i * labelSpacing)
val x = 0F.minus(spacing)
val labelValue = minLabelValue + ((i * labelRange) / (maxLabelCount - 1))
val text = if (labelValue.toString().length > 4) {
labelValue.toString().take(4)
} else {
labelValue.toString()
}
drawContext.canvas.nativeCanvas.drawText(
text,
x,
y,
textPaint
)
}
}
@@ -0,0 +1,47 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.common.ui
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
fun DrawScope.drawGridLines(width: Float, height: Float, spacing: Float) {
// Calculate the dimensions of the grid area by subtracting the spacing
val newHeight = height.minus(spacing)
val newWidth = width.minus(spacing)
// Calculate the spacing between horizontal and vertical grid lines
val horizontalGridSpacing = newHeight / 5
val verticalGridSpacing = newWidth / 4
// Draw horizontal grid lines
repeat(4) { i ->
val y = (i + 1) * horizontalGridSpacing
drawLine(
start = Offset(0F, y + spacing),
end = Offset(newWidth + spacing, y + spacing),
color = Color.LightGray,
strokeWidth = 1f
)
}
// Draw vertical grid lines
repeat(3) { i ->
val x = (i + 1) * verticalGridSpacing
drawLine(
start = Offset(x + spacing, 0F),
end = Offset(x + spacing, newHeight + spacing),
color = Color.LightGray,
strokeWidth = 1f
)
}
}
@@ -0,0 +1,188 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.gauge
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
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.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import com.himanshoe.charty.gauge.config.GaugeChartConfig
import com.himanshoe.charty.gauge.config.GaugeChartDefaults
import com.himanshoe.charty.gauge.config.NeedleConfig
import kotlin.math.cos
import kotlin.math.sin
private const val START_ANGLE = 135F
private const val INITIAL_START_ANGLE = 405F
/**
* A composable function that displays a gauge chart.
*
* @param percentValue The value in percentage to be displayed on the gauge chart.
* @param modifier The modifier for styling or positioning the gauge chart.
* @param gaugeChartConfig The configuration for the gauge chart appearance.
* @param needleConfig The configuration for the needle in the gauge chart.
* @param animated Determines whether the gauge chart should be animated.
* @param animationSpec The animation specification for the gauge chart animation.
*/
@Composable
fun GaugeChart(
percentValue: Int,
modifier: Modifier = Modifier,
gaugeChartConfig: GaugeChartConfig = GaugeChartDefaults.gaugeConfigDefaults(),
needleConfig: NeedleConfig = GaugeChartDefaults.needleConfigDefaults(),
animated: Boolean = true,
animationSpec: AnimationSpec<Float> = tween(),
) {
require(percentValue in 1..100) { "percentValue must be within the range of 1 to 100" }
val animatedPercent = rememberAnimatedPercent(animated, percentValue, animationSpec)
Box(modifier = modifier.aspectRatio(1f)) {
Canvas(modifier = Modifier.fillMaxSize()) {
val centerX = size.width / 2
val centerY = size.height / 2
val radius = (size.width - gaugeChartConfig.strokeWidth) / 2
val sweepAngle = INITIAL_START_ANGLE - START_ANGLE
// Draw background arc
drawArc(
color = gaugeChartConfig.placeHolderColor,
startAngle = START_ANGLE,
sweepAngle = sweepAngle,
useCenter = false,
topLeft = Offset(centerX - radius, centerY - radius),
size = Size(radius * 2, radius * 2),
style = Stroke(width = gaugeChartConfig.strokeWidth, cap = StrokeCap.Round)
)
if (gaugeChartConfig.showIndicator) {
// Draw minute hour dividers
val dividerCount = 100
val dividerSweepAngle = sweepAngle / dividerCount
val longerDividerLength = radius * 0.1f
val shorterDividerLength = radius * 0.05f
for (i in 0..dividerCount) { // Updated loop range to include 0
val dividerStartAngle = START_ANGLE + (i * dividerSweepAngle)
val isLongerDivider = i % 10 == 0
val dividerLength =
if (isLongerDivider) longerDividerLength else shorterDividerLength
val startPoint = polarToCartesian(
centerX,
centerY,
radius - gaugeChartConfig.strokeWidth,
dividerStartAngle
)
val endPoint = polarToCartesian(
centerX,
centerY,
radius - gaugeChartConfig.strokeWidth - dividerLength,
dividerStartAngle
)
val color =
if (isLongerDivider) gaugeChartConfig.indicatorColor else gaugeChartConfig.indicatorColor.copy(
alpha = 0.8F
)
drawLine(
color = color,
start = Offset(startPoint.x, startPoint.y),
end = Offset(endPoint.x, endPoint.y),
strokeWidth = gaugeChartConfig.indicatorWidth,
cap = StrokeCap.Round
)
}
}
val currentSweepAngle = animatedPercent * sweepAngle
drawArc(
color = gaugeChartConfig.primaryColor,
startAngle = START_ANGLE,
sweepAngle = currentSweepAngle,
useCenter = false,
topLeft = Offset(centerX - radius, centerY - radius),
size = Size(radius * 2, radius * 2),
style = Stroke(width = gaugeChartConfig.strokeWidth, cap = StrokeCap.Round)
)
if (gaugeChartConfig.showNeedle) {
// Calculate needle path
val needleAngle = START_ANGLE + currentSweepAngle
val needlePath = Path().apply {
val startPoint =
polarToCartesian(centerX, centerY, radius.div(1.2F), needleAngle)
val endPoint = polarToCartesian(centerX, centerY, radius.div(1.2F), needleAngle)
moveTo(centerX, centerY)
lineTo(startPoint.x, startPoint.y)
lineTo(endPoint.x, endPoint.y)
}
// Draw needle
drawPath(
path = needlePath,
color = needleConfig.color,
style = Stroke(width = needleConfig.strokeWidth, cap = StrokeCap.Round)
)
}
}
}
}
private fun polarToCartesian(
centerX: Float,
centerY: Float,
radius: Float,
angle: Float
): Offset {
val x = centerX + radius * cos(Math.toRadians(angle.toDouble())).toFloat()
val y = centerY + radius * sin(Math.toRadians(angle.toDouble())).toFloat()
return Offset(x, y)
}
@Composable
private fun rememberAnimatedPercent(
animated: Boolean,
percentValue: Int,
animationSpec: AnimationSpec<Float>
): Float {
val animatedPercent = remember {
Animatable(initialValue = 0f)
}
LaunchedEffect(Unit) {
if (animated) {
animatedPercent.animateTo(
targetValue = percentValue / 100f,
animationSpec = animationSpec
)
} else {
animatedPercent.snapTo(percentValue / 100f)
}
}
return animatedPercent.value
}
@Preview
@Composable
fun GaugeChartPreview() {
val percentValue = 100
GaugeChart(percentValue = percentValue)
}
@@ -0,0 +1,34 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.gauge.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Represents the configuration for a gauge chart.
*
* @param placeHolderColor The color of the placeholder arc in the gauge chart.
* @param primaryColor The color of the primary arc in the gauge chart.
* @param strokeWidth The width of the arcs in the gauge chart.
* @param showNeedle Indicates whether to show the needle in the gauge chart.
* @param showIndicator Indicates whether to show the indicator in the gauge chart.
* @param indicatorColor The color of the indicator in the gauge chart.
* @param indicatorWidth The width of the indicator in the gauge chart.
*/
@Immutable
data class GaugeChartConfig(
val placeHolderColor: Color,
val primaryColor: Color,
val strokeWidth: Float,
val showNeedle: Boolean,
val showIndicator: Boolean,
val indicatorColor: Color,
val indicatorWidth: Float,
)
@@ -0,0 +1,42 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.gauge.config
import androidx.compose.ui.graphics.Color
/**
* Provides default configurations for the gauge chart.
*/
object GaugeChartDefaults {
/**
* Returns the default configuration for the gauge chart.
*
* @return The default [GaugeChartConfig] for the gauge chart.
*/
fun gaugeConfigDefaults() = GaugeChartConfig(
primaryColor = Color(0xFF20A100),
placeHolderColor = Color(0xFFABEDCD),
strokeWidth = 48F,
showNeedle = true,
showIndicator = true,
indicatorColor = Color(0xffed625d),
indicatorWidth = 8F
)
/**
* Returns the default configuration for the needle in the gauge chart.
*
* @return The default [NeedleConfig] for the needle in the gauge chart.
*/
fun needleConfigDefaults() = NeedleConfig(
color = Color(0xFF93A047),
strokeWidth = 40f,
)
}
@@ -0,0 +1,24 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.gauge.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Configuration for the needle in the gauge chart.
*
* @property color The color of the needle.
* @property strokeWidth The width of the needle stroke.
*/
@Immutable
data class NeedleConfig(
val color: Color,
val strokeWidth: Float,
)
@@ -0,0 +1,204 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.group
import android.graphics.Paint
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.ComposeList
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.config.ChartyLabelTextConfig
import com.himanshoe.charty.common.toComposeList
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.group.model.GroupBarData
/**
* Composable function that renders a grouped bar chart.
*
* @param groupBarDataCollection The collection of group bar chart data.
* @param modifier The modifier for the chart.
* @param padding The padding around the chart. Defaults to 16.dp.
* @param barWidthRatio The ratio of the bar width to the available space. Defaults to 0.8f.
* @param axisConfig The configuration for the chart's axis. Defaults to [ChartDefaults.axisConfigDefaults].
* @param textLabelTextConfig The configuration for the text labels in the chart. Defaults to [ChartDefaults.defaultTextLabelConfig].
*/
@Composable
fun GroupedBarChart(
groupBarDataCollection: ComposeList<GroupBarData>,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
barWidthRatio: Float = 0.8f,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
textLabelTextConfig: ChartyLabelTextConfig = ChartDefaults.defaultTextLabelConfig(),
) {
require(barWidthRatio in 0.4f..0.9f) { "barWidthRatio must be within the range of 0.4F to 0.9F, but use 0.8F for best looking View" }
val allDataPoints = groupBarDataCollection.data.flatMap { it.dataPoints }
val maxValue = allDataPoints.maxOrNull() ?: 0f
val minValue = allDataPoints.minOrNull() ?: 0f
val newItems = if (allDataPoints.min() > 0F) {
listOf(0F) + allDataPoints
} else {
allDataPoints
}
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
ChartSurface(
modifier = modifier,
padding = padding,
axisConfig = axisConfig,
chartData = newItems.toComposeList(),
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val groupCount = groupBarDataCollection.data.size
val barCount = groupBarDataCollection.data.firstOrNull()?.dataPoints?.size ?: 0
val groupWidth = chartWidth / groupCount
val totalBarWidth = groupWidth * barWidthRatio
val barWidth = totalBarWidth / barCount.toFloat()
groupBarDataCollection.data.fastForEachIndexed { groupIndex, groupedBarData ->
if (axisConfig.showGridLabel) {
drawLabel(
groupedBarData = groupedBarData,
groupIndex = groupIndex,
groupWidth = groupWidth,
chartHeight = chartHeight,
textLabelTextConfig = textLabelTextConfig
)
}
groupedBarData.dataPoints.fastForEachIndexed { barIndex, barValue ->
val x = calculateBarX(groupIndex, groupWidth, barWidthRatio, barIndex, barWidth)
val barHeight = calculateBarHeight(barValue, minValue, maxValue, chartHeight)
drawGroupedBar(
x = x,
barHeight = barHeight,
width = barWidth,
height = chartHeight,
colors = groupedBarData.colors,
barIndex = barIndex
)
}
}
}
}
}
private fun DrawScope.drawLabel(
groupedBarData: GroupBarData,
groupIndex: Int,
groupWidth: Float,
chartHeight: Float,
textLabelTextConfig: ChartyLabelTextConfig
) {
val label = groupedBarData.label
val labelX = (groupIndex * groupWidth) + (groupWidth / 2f)
val labelY = chartHeight + size.width / 20
drawIntoCanvas {
it.nativeCanvas.drawText(
label,
labelX,
labelY,
Paint().apply {
color = textLabelTextConfig.textColor.toArgb()
textAlign = Paint.Align.CENTER
textSize = size.width / 35
}
)
}
}
private fun calculateBarX(
groupIndex: Int,
groupWidth: Float,
barWidthRatio: Float,
barIndex: Int,
barWidth: Float
) = (groupIndex * groupWidth) +
((1 - barWidthRatio) / 2) *
groupWidth +
(barIndex * barWidth) + (barWidth / 2f)
private fun calculateBarHeight(
barValue: Float,
minValue: Float,
maxValue: Float,
chartHeight: Float
): Float {
val newMinValue = if (minValue < 0) minValue else 0F
val range = maxValue - newMinValue
val normalizedValue = barValue - newMinValue
val heightRatio = if (range != 0f) normalizedValue / range else 0f
return heightRatio * chartHeight
}
private fun DrawScope.drawGroupedBar(
x: Float,
barHeight: Float,
width: Float,
height: Float,
colors: List<Color>,
barIndex: Int
) {
val barRect = Rect(
left = x - (width / 2f),
top = height - barHeight,
right = x + (width / 2f),
bottom = height
)
drawRoundRect(
color = colors.getOrElse(barIndex % colors.size) { Color.Gray },
topLeft = Offset(barRect.left, barRect.top),
size = Size(barRect.width, barRect.height),
)
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.group.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Immutable data class representing a group bar chart data.
*
* @param label The label for the group bar.
* @param dataPoints The list of data points for each bar in the group.
* @param colors The list of colors for each bar in the group. Defaults to [Color.Transparent]
* if not provided explicitly.
*/
@Immutable
data class GroupBarData(
val label: String,
val dataPoints: List<Float>,
val colors: List<Color> = List(dataPoints.count()) { Color.Transparent }
)
@@ -0,0 +1,200 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line
import android.graphics.PointF
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.math.chartDataToOffset
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.line.config.CurvedLineChartColors
import com.himanshoe.charty.line.config.CurvedLineChartDefaults
import com.himanshoe.charty.line.config.LineChartDefaults
import com.himanshoe.charty.line.config.LineConfig
/**
* A composable function that displays a curved line chart.
*
* @param dataCollection The collection of chart data points.
* @param modifier The modifier for the chart.
* @param padding The padding around the chart.
* @param axisConfig The configuration for the chart's axes.
* @param radiusScale The scale factor for the radius of the data points.
* @param lineConfig The configuration for the line in the chart.
* @param chartColors The colors used in the chart.
*/
@Composable
fun CurveLineChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
radiusScale: Float = 0.02f,
lineConfig: LineConfig = LineChartDefaults.defaultConfig(),
chartColors: CurvedLineChartColors = CurvedLineChartDefaults.defaultColor(),
) {
val points = dataCollection.data
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
var pointBound by remember { mutableStateOf(0F) }
val horizontalScale = chartWidth.div(points.count())
val verticalScale = chartHeight.div((dataCollection.maxYValue() - dataCollection.minYValue()))
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val contentColor = Brush.linearGradient(chartColors.contentColor)
val backgroundColor = Brush.linearGradient(chartColors.backgroundColors)
val dotColor = Brush.linearGradient(chartColors.dotColor)
val minYValue = dataCollection.minYValue()
Canvas(
modifier = Modifier
.background(backgroundColor)
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
pointBound = size.width.div(
points
.count()
.times(1.2F)
)
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val graphPathPoints = mutableListOf<PointF>()
val radius = size.width * radiusScale
Path().apply {
moveTo(0f, size.height) // Start from the bottom-left corner of the canvas
val firstData = dataCollection.data.first()
val initialX = 0f
val initialY = size.height - ((firstData.yValue - minYValue) * verticalScale)
lineTo(initialX, initialY) // Move to the initial point
dataCollection.data.fastForEachIndexed { index, data ->
val centerOffset = chartDataToOffset(
index,
pointBound,
size,
data.yValue,
horizontalScale,
)
val x = centerOffset.x
val y = size.height - ((data.yValue - minYValue) * verticalScale)
val innerX =
x.coerceIn(centerOffset.x - radius / 2, centerOffset.x + radius / 2)
val innerY = y.coerceIn(radius, size.height - radius)
graphPathPoints.add(PointF(innerX, innerY))
if (index > 0) {
val prevIndex = index.minus(1)
val previousCenterOffset = chartDataToOffset(
index.minus(1),
pointBound,
size,
data.yValue,
horizontalScale,
)
val prevX = previousCenterOffset.x
val prevY =
size.height - (
dataCollection.data[prevIndex].yValue -
dataCollection.minYValue()
) * verticalScale
val prevInnerX = prevX.coerceIn(
prevX - radiusScale * size.width,
prevX + radiusScale * size.width
)
val prevInnerY = prevY.coerceIn(0f, size.height)
cubicTo(
prevInnerX + (innerX - prevInnerX) / 2, prevInnerY,
prevInnerX + (innerX - prevInnerX) / 2, innerY,
innerX, innerY
)
}
if (points.count() < 14) {
drawXAxisLabels(
data = data.xValue,
center = centerOffset,
count = points.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
}
// Close the path
lineTo(size.width, size.height)
lineTo(0f, size.height)
close()
// Draw the background path
drawPath(
path = this,
brush = contentColor
)
}
if (lineConfig.hasDotMarker) {
graphPathPoints.fastForEach { point ->
drawCircle(
brush = dotColor,
radius = radiusScale * size.width,
center = Offset(point.x, point.y)
)
}
}
}
}
}
@@ -0,0 +1,258 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line
import android.graphics.PointF
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.math.chartDataToOffset
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.common.toChartDataCollection
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.line.config.LineChartColors
import com.himanshoe.charty.line.config.LineChartDefaults
import com.himanshoe.charty.line.config.LineConfig
import com.himanshoe.charty.line.model.LineData
/**
* A composable function that displays a line chart.
*
* @param dataCollection The collection of chart data points.
* @param dotColor The color of the data points.
* @param lineColor The color of the line in the chart.
* @param modifier The modifier for the chart.
* @param backgroundColor The background color of the chart.
* @param padding The padding around the chart.
* @param axisConfig The configuration for the chart's axes.
* @param lineConfig The configuration for the line in the chart.
* @param radiusScale The scale factor for the radius of the data points.
*/
@Composable
fun LineChart(
dataCollection: ChartDataCollection,
dotColor: Color,
lineColor: Color,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.White,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
lineConfig: LineConfig = LineChartDefaults.defaultConfig(),
radiusScale: Float = 0.02f,
) {
LineChart(
dataCollection = dataCollection,
modifier = modifier,
radiusScale = radiusScale,
padding = padding,
axisConfig = axisConfig,
lineConfig = lineConfig,
chartColors = LineChartColors(
dotColor = listOf(dotColor, dotColor),
lineColor = listOf(lineColor, lineColor),
backgroundColors = listOf(backgroundColor, backgroundColor)
)
)
}
/**
* A composable function that displays a line chart.
*
* @param dataCollection The collection of chart data points.
* @param modifier The modifier for the chart.
* @param padding The padding around the chart.
* @param axisConfig The configuration for the chart's axes.
* @param radiusScale The scale factor for the radius of the data points.
* @param lineConfig The configuration for the line in the chart.
* @param chartColors The colors used in the chart.
*/
@Composable
fun LineChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
radiusScale: Float = 0.02f,
lineConfig: LineConfig = LineChartDefaults.defaultConfig(),
chartColors: LineChartColors = LineChartDefaults.defaultColor(),
) {
val points = dataCollection.data
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
var pointBound by remember { mutableStateOf(0F) }
val horizontalScale = chartWidth.div(points.count())
val verticalScale = chartHeight.div((dataCollection.maxYValue() - dataCollection.minYValue()))
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val lineColor = Brush.linearGradient(chartColors.lineColor)
val backgroundColor = Brush.linearGradient(chartColors.backgroundColors)
val dotColor = Brush.linearGradient(chartColors.dotColor)
val minYValue = dataCollection.minYValue()
Canvas(
modifier = Modifier
.background(backgroundColor)
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
pointBound = size.width.div(
points
.count()
.times(1.2F)
)
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val graphPathPoints = mutableListOf<PointF>()
val radius = size.width * radiusScale
Path().apply {
val firstData = points.first()
val initialX = 0f
val initialY = size.height - ((firstData.yValue - minYValue) * verticalScale)
moveTo(initialX, initialY) // Move to the initial point
points.fastForEachIndexed { index, data ->
val centerOffset = chartDataToOffset(
index,
pointBound,
size,
data.yValue,
horizontalScale,
)
val x = centerOffset.x
val y = size.height - ((data.yValue - minYValue) * verticalScale)
val innerX =
x.coerceIn(centerOffset.x - radius / 2, centerOffset.x + radius / 2)
val innerY = y.coerceIn(radius, size.height - radius)
graphPathPoints.add(PointF(innerX, innerY))
if (points.size > 1) {
when (index) {
0 -> moveTo(x, y)
else -> lineTo(innerX, innerY)
}
}
if (points.count() < 14) {
drawXAxisLabels(
data = data.xValue,
center = centerOffset,
count = points.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
}
// Close the path
lineTo(size.width, size.height)
close()
val pathEffect =
if (lineConfig.hasSmoothCurve) PathEffect.cornerPathEffect(radius) else null
// Draw the background path
drawPath(
path = this,
brush = lineColor,
style = Stroke(width = lineConfig.strokeSize, pathEffect = pathEffect),
)
}
if (lineConfig.hasDotMarker) {
graphPathPoints.fastForEach { point ->
drawCircle(
brush = dotColor,
radius = radiusScale * size.width,
center = Offset(point.x, point.y)
)
}
}
}
}
}
@Composable
@Preview
private fun LineChartPreview(modifier: Modifier = Modifier) {
Column(modifier) {
CurveLineChart(
dataCollection = generateMockPointList().toChartDataCollection(),
modifier = Modifier
.size(450.dp),
)
}
}
private fun generateMockPointList(): List<LineData> {
return listOf(
LineData(0F, "Jan"),
LineData(10F, "Feb"),
LineData(05F, "Mar"),
LineData(50F, "Apr"),
LineData(03F, "June"),
LineData(9F, "July"),
LineData(40F, "Aug"),
LineData(60F, "Sept"),
LineData(33F, "Oct"),
LineData(11F, "Nov"),
LineData(27F, "Dec"),
LineData(10F, "Jan"),
LineData(13F, "Oct"),
LineData(-10F, "Nov"),
LineData(0F, "Dec"),
LineData(10F, "Jan"),
)
}
@@ -0,0 +1,37 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line.config
import androidx.compose.ui.graphics.Color
/**
* Default configuration values for a curved line chart.
*/
object CurvedLineChartDefaults {
/**
* Returns the default colors for the curved line chart.
*
* @return The default colors for the curved line chart.
*/
fun defaultColor() = CurvedLineChartColors(
contentColor = listOf(
Color(0xffed625d),
Color(0xfff79f88)
),
dotColor = listOf(
Color(0xff50c0a8),
Color(0xff7a57e3),
),
backgroundColors = listOf(
Color.White,
Color.White,
),
)
}
@@ -0,0 +1,40 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Represents the colors used in a line chart.
*
* @property lineColor The colors of the lines in the chart.
* @property dotColor The colors of the dots in the chart.
* @property backgroundColors The background colors of the chart.
*/
@Immutable
data class LineChartColors(
val lineColor: List<Color> = emptyList(),
val dotColor: List<Color> = emptyList(),
val backgroundColors: List<Color> = emptyList(),
)
/**
* Represents the colors used in a curved line chart.
*
* @property dotColor The colors of the dots in the chart.
* @property backgroundColors The background colors of the chart.
* @property contentColor The colors of the content (lines) in the chart.
*/
@Immutable
data class CurvedLineChartColors(
val dotColor: List<Color> = emptyList(),
val backgroundColors: List<Color> = emptyList(),
val contentColor: List<Color> = emptyList(),
)
@@ -0,0 +1,48 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line.config
import androidx.compose.ui.graphics.Color
/**
* Default configurations and colors for a line chart.
*/
object LineChartDefaults {
/**
* Returns the default configuration for a line chart.
*
* @return The default line chart configuration.
*/
fun defaultConfig() = LineConfig(
hasSmoothCurve = true,
hasDotMarker = true,
strokeSize = 5F
)
/**
* Returns the default colors for a line chart.
*
* @return The default line chart colors.
*/
fun defaultColor() = LineChartColors(
lineColor = listOf(
Color(0xffed625d),
Color(0xfff79f88)
),
dotColor = listOf(
Color(0xff50c0a8),
Color(0xff7a57e3),
),
backgroundColors = listOf(
Color.White,
Color.White,
)
)
}
@@ -0,0 +1,25 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line.config
import androidx.compose.runtime.Immutable
/**
* Configuration options for a line in a line chart.
*
* @property hasSmoothCurve Whether the line should have a smooth curve.
* @property hasDotMarker Whether the line should have a dot marker at each data point.
* @property strokeSize The size of the line stroke.
*/
@Immutable
data class LineConfig(
val hasSmoothCurve: Boolean,
val hasDotMarker: Boolean,
val strokeSize: Float
)
@@ -0,0 +1,24 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.line.model
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.ChartData
/**
* Represents a data point for a line chart.
*
* @property yValue The y-axis value of the data point.
* @property xValue The x-axis value of the data point.
*/
@Immutable
data class LineData(override val yValue: Float, override val xValue: Any) : ChartData {
override val chartString: String
get() = "Line Chart"
}
@@ -0,0 +1,151 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.pie
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.config.ChartyLabelTextConfig
import com.himanshoe.charty.common.toChartDataCollection
import com.himanshoe.charty.pie.config.PieChartConfig
import com.himanshoe.charty.pie.config.PieChartDefaults
import com.himanshoe.charty.pie.model.PieData
/**
* Displays a pie chart based on the provided data collection.
*
* @param dataCollection The collection of data points for the pie chart.
* @param modifier The modifier for styling the pie chart.
* @param textLabelTextConfig The configuration for the text labels in the pie chart.
* @param pieChartConfig The configuration for the pie chart.
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun PieChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
textLabelTextConfig: ChartyLabelTextConfig = ChartDefaults.defaultTextLabelConfig(),
pieChartConfig: PieChartConfig = PieChartDefaults.defaultConfig(),
) {
val pieChartData = dataCollection.data.fastMap { it.yValue }
val total = pieChartData.sum()
var startAngle = pieChartConfig.startAngle.angle
Column(modifier) {
Canvas(
modifier = Modifier
.aspectRatio(1F)
) {
val radius = size.minDimension / 3
val centerX = size.width / 2
val centerY = size.height / 2
val holeRadius =
if (pieChartConfig.donut) radius * 0.4f else 0f // Adjust the hole radius based on donut config
dataCollection.data.fastForEach { value ->
if (value is PieData) {
val sweepAngle = (value.yValue / total) * 360
if (pieChartConfig.donut.not()) {
drawArc(
color = value.color,
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = true,
)
} else {
drawArc(
color = value.color,
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = false,
topLeft = Offset(centerX - radius, centerY - radius),
size = Size(radius * 2, radius * 2),
style = Stroke(width = radius - holeRadius)
)
}
startAngle += sweepAngle
}
}
}
if (pieChartConfig.showLabel) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 4.dp),
horizontalArrangement = Arrangement.Center
) {
dataCollection.data.fastMap {
if (it is PieData) {
Box(
Modifier
.size(textLabelTextConfig.indicatorSize)
.clip(CircleShape)
.align(Alignment.CenterVertically)
.background(it.color)
)
Text(
text = it.xValue.toString(),
fontSize = textLabelTextConfig.textSize,
fontStyle = textLabelTextConfig.fontStyle,
fontWeight = textLabelTextConfig.fontWeight,
fontFamily = textLabelTextConfig.fontFamily,
maxLines = textLabelTextConfig.maxLine,
overflow = textLabelTextConfig.overflow,
modifier = Modifier.padding(
end = 8.dp,
start = 4.dp
)
)
}
}
}
}
}
}
@Composable
@Preview
private fun PieChartPreview() {
val data = listOf(
PieData(30f, "Category A", Color.Blue),
PieData(20f, "Category B", Color.Red),
PieData(10f, "Category C", Color.Green),
PieData(10f, "Category C", Color.Black),
)
PieChart(
dataCollection = data.toChartDataCollection(),
modifier = Modifier.wrapContentSize()
)
}
@@ -0,0 +1,26 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.pie.config
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.config.StartAngle
/**
* Immutable data class representing the configuration options for a pie chart.
*
* @param donut Indicates whether the pie chart should be rendered as a donut.
* @param showLabel Indicates whether labels should be shown for each pie slice.
* @param startAngle The starting angle of the pie chart.
*/
@Immutable
data class PieChartConfig(
val donut: Boolean,
val showLabel: Boolean,
val startAngle: StartAngle = StartAngle.Zero
)
@@ -0,0 +1,22 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.pie.config
/**
* Object providing default configurations for a pie chart.
*/
object PieChartDefaults {
/**
* Returns the default configuration for a pie chart.
*
* @return The default configuration.
*/
fun defaultConfig() = PieChartConfig(donut = true, showLabel = true)
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.pie.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import com.himanshoe.charty.common.ChartData
/**
* Represents a data point in a pie chart.
*
* @property yValue The value of the data point.
* @property xValue The label or identifier of the data point.
* @property color The color associated with the data point.
*/
@Immutable
data class PieData(override val yValue: Float, override val xValue: Any, val color: Color) :
ChartData {
override val chartString: String
get() = "Pie Chart"
}
@@ -0,0 +1,223 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.point
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartColors
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.math.chartDataToOffset
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.common.toChartDataCollection
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawXAxisLabels
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.point.config.PointType
import com.himanshoe.charty.point.model.PointData
/**
* Displays a point chart using the provided data.
*
* @param dataCollection The collection of chart data.
* @param contentColor The color of the chart points.
* @param modifier The modifier for the chart.
* @param backgroundColor The background color of the chart.
* @param padding The padding around the chart.
* @param pointType The type of point to use in the chart.
* @param axisConfig The configuration for the chart axes.
* @param radiusScale The scale factor for the point radius.
*/
@Composable
fun PointChart(
dataCollection: ChartDataCollection,
contentColor: Color,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.White,
padding: Dp = 16.dp,
pointType: PointType = PointType.Stroke(),
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
radiusScale: Float = 0.02f,
) {
PointChart(
dataCollection = dataCollection,
modifier = modifier,
padding = padding,
pointType = pointType,
axisConfig = axisConfig,
radiusScale = radiusScale,
chartColors = ChartColors(
contentColor = listOf(contentColor, contentColor),
backgroundColors = listOf(backgroundColor, backgroundColor)
)
)
}
/**
* Displays a point chart using the provided data.
*
* @param dataCollection The collection of chart data.
* @param modifier The modifier for the chart.
* @param padding The padding around the chart.
* @param pointType The type of point to use in the chart.
* @param axisConfig The configuration for the chart axes.
* @param radiusScale The scale factor for the point radius.
* @param chartColors The colors used in the chart.
*/
@Composable
fun PointChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
pointType: PointType = PointType.Stroke(),
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
radiusScale: Float = 0.02f,
chartColors: ChartColors = ChartDefaults.colorDefaults(),
) {
val points = dataCollection.data
var chartWidth by remember { mutableStateOf(0F) }
var chartHeight by remember { mutableStateOf(0F) }
var pointBound by remember { mutableStateOf(0F) }
val horizontalScale = chartWidth.div(points.count())
val verticalScale = chartHeight.div((dataCollection.maxYValue() - dataCollection.minYValue()))
ChartSurface(
padding = padding,
chartData = dataCollection,
modifier = modifier,
axisConfig = axisConfig
) {
val contentColor = Brush.linearGradient(chartColors.contentColor)
val backgroundColor = Brush.linearGradient(chartColors.backgroundColors)
val minYValue = dataCollection.minYValue()
Canvas(
modifier = Modifier
.background(backgroundColor)
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
pointBound = size.width.div(
points
.count()
.times(1.2F)
)
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val radius = chartWidth * radiusScale
points.fastForEachIndexed { index, point ->
val centerOffset = chartDataToOffset(
index,
pointBound,
size,
point.yValue,
horizontalScale,
)
val x = centerOffset.x
val y = chartHeight - ((point.yValue - minYValue) * verticalScale)
val clampedX = x.coerceIn(centerOffset.x - radius / 2, centerOffset.x + radius / 2)
val clampedY = y.coerceIn(radius, chartHeight - radius)
val style = when (pointType) {
is PointType.Stroke -> Stroke(width = pointType.strokeWidth)
else -> Fill
}
drawCircle(
center = Offset(clampedX, clampedY),
style = style,
radius = radius,
brush = contentColor
)
if (points.count() < 14) {
drawXAxisLabels(
data = point.xValue,
center = centerOffset,
count = points.count(),
padding = padding.toPx(),
minLabelCount = axisConfig.minLabelCount,
)
}
}
}
}
}
@Composable
@Preview
private fun PointChartPreview(modifier: Modifier = Modifier) {
Column(modifier) {
PointChart(
dataCollection = generateMockPointList().toChartDataCollection(),
modifier = Modifier
.size(450.dp),
contentColor = Color.Red,
)
}
}
private fun generateMockPointList(): List<PointData> {
return listOf(
PointData(-10F, "Jan"),
PointData(10F, "Feb"),
PointData(05F, "Mar"),
PointData(50F, "Apr"),
PointData(03F, "June"),
PointData(9F, "July"),
PointData(40F, "Aug"),
PointData(60F, "Sept"),
PointData(33F, "Oct"),
PointData(11F, "Nov"),
PointData(27F, "Dec"),
PointData(10F, "Jan"),
PointData(73F, "Oct"),
PointData(-20F, "Nov"),
PointData(0F, "Dec"),
PointData(10F, "Jan"),
)
}
@@ -0,0 +1,29 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.point.config
import androidx.compose.runtime.Immutable
/**
* Represents the type of a point in a chart.
*/
sealed interface PointType {
/**
* A filled point type.
*/
object Fill : PointType
/**
* A stroke point type with a specified stroke width.
*
* @param strokeWidth The stroke width for the point.
*/
@Immutable
data class Stroke(val strokeWidth: Float = 4F) : PointType
}
@@ -0,0 +1,27 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.point.model
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.ChartData
/**
* Represents data for a point in a chart.
*
* @param yValue The y-value of the point.
* @param xValue The x-value of the point.
*/
@Immutable
data class PointData(override val yValue: Float, override val xValue: Any) : ChartData {
/**
* Returns the string representation of the chart type.
*/
override val chartString: String
get() = "Point Chart"
}
@@ -0,0 +1,243 @@
package com.himanshoe.charty.radar
import android.graphics.Paint
import android.graphics.PointF
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartDataCollection
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.maxYValue
import com.himanshoe.charty.common.minYValue
import com.himanshoe.charty.radar.config.RadarChartColors
import com.himanshoe.charty.radar.config.RadarChartDefaults
import com.himanshoe.charty.radar.config.RadarConfig
import com.himanshoe.charty.radar.model.RadarData
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
/**
* A composable function that displays a radar chart.
*
* @param dataCollection The collection of chart data points.
* @param modifier The modifier for the chart.
* @param axisConfig The configuration for the chart's axes.
* @param radarConfig The configuration for the polygon in the chart.
* @param chartColors The colors used in the chart.
* @param radiusScale The scale factor for the radius of the data points.
*/
@Composable
fun RadarChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
radarConfig: RadarConfig = RadarChartDefaults.defaultConfig(),
chartColors: RadarChartColors = RadarChartDefaults.defaultColor(),
radiusScale: Float = 0.02f,
) {
val maxYValue by remember { mutableStateOf(dataCollection.maxYValue()) }
val minYValue by remember { mutableStateOf(dataCollection.minYValue()) }
val diff = abs(maxYValue * 0.1F)
val dataRange = (minYValue - diff)..(maxYValue + diff)
val partitionAngle = (2 * Math.PI / dataCollection.data.size).toFloat()
Canvas(
modifier = modifier
.drawBehind {
drawRadarAxis(
dataCollection = dataCollection,
dataRange = dataRange,
radius = size.minDimension / 3,
center = PointF(size.width / 2, size.height / 2),
axisConfig = axisConfig,
)
}
) {
val radius = size.minDimension / 3
val centerX = size.width / 2
val centerY = size.height / 2
val scaleFactor = radius / (dataRange.endInclusive - dataRange.start)
val lineColor = Brush.linearGradient(chartColors.lineColor)
val dotColor = Brush.linearGradient(chartColors.dotColor)
val radarPolygonVertices = mutableListOf<PointF>()
Path().apply {
dataCollection.data.fastForEachIndexed { index, chartData ->
val angle = index * partitionAngle
val x = centerX + scaleFactor * (chartData.yValue - dataRange.start) * cos(angle)
val y = centerY + scaleFactor * (chartData.yValue - dataRange.start) * sin(angle)
if (index == 0) {
moveTo(x, y)
} else {
lineTo(x, y)
}
radarPolygonVertices.add(PointF(x, y))
}
close()
drawPath(
path = this,
brush = lineColor,
style = Stroke(width = radarConfig.strokeSize)
)
if (radarConfig.fillPolygon) {
drawPath(
path = this,
brush = Brush.linearGradient(chartColors.fillColor),
)
}
}
if (radarConfig.hasDotMarker) {
radarPolygonVertices.fastForEach {
drawCircle(
brush = dotColor,
radius = radiusScale * size.width,
center = Offset(it.x, it.y)
)
}
}
}
}
private fun DrawScope.drawRadarAxis(
dataCollection: ChartDataCollection,
dataRange: ClosedFloatingPointRange<Float>,
radius: Float,
center: PointF,
axisConfig: AxisConfig,
) {
val partitionAngle = (2 * Math.PI / dataCollection.data.size).toFloat()
val axisPolygons = if (axisConfig.showGridLines) {
listOf(Path(), Path(), Path(), Path())
} else {
emptyList()
}
for (spokeIndex in 0 until dataCollection.data.size) {
val angle = spokeIndex * partitionAngle
if (axisConfig.showAxes) {
drawLine(
start = Offset(center.x, center.y),
end = Offset(center.x + radius * cos(angle), center.y + radius * sin(angle)),
color = axisConfig.axisColor,
strokeWidth = axisConfig.axisStroke
)
}
drawContext.canvas.nativeCanvas.drawText(
dataCollection.data[spokeIndex].xValue.toString(),
center.x + (radius + 30F) * cos(angle),
center.y + (radius + 30F) * sin(angle),
Paint().apply {
this.color = axisConfig.axisColor.toArgb()
this.textSize = size.width / 30
this.textAlign = if (angle > Math.PI / 2 && angle < 3 * Math.PI / 2) {
Paint.Align.RIGHT
} else {
Paint.Align.LEFT
}
}
)
axisPolygons.fastForEachIndexed { i, path ->
val scale = 1F - 0.25F * i
if (spokeIndex == 0) {
path.moveTo(center.x + scale * radius * cos(angle), center.y + scale * radius * sin(angle))
} else {
path.lineTo(center.x + scale * radius * cos(angle), center.y + scale * radius * sin(angle))
}
}
}
if (!axisConfig.showGridLines) return
axisPolygons.fastForEach {
it.close()
drawPath(
path = it,
color = axisConfig.gridColor,
style = Stroke(width = axisConfig.axisStroke)
)
}
if (!axisConfig.showGridLabel) return
drawGridLabels(
radius = radius,
center = center,
dataRange = dataRange,
angle = (partitionAngle / 2),
labelCount = axisPolygons.size,
labelColor = axisConfig.gridColor
)
}
private fun DrawScope.drawGridLabels(
radius: Float,
center: PointF,
dataRange: ClosedFloatingPointRange<Float>,
angle: Float,
labelCount: Int,
labelColor: Color
) {
val reduceBy = 1F / labelCount
for (i in 0 until labelCount) {
val scale = 1F - reduceBy * i
drawContext.canvas.nativeCanvas.drawText(
(dataRange.start + (dataRange.endInclusive - dataRange.start) * scale).toString(),
center.x + scale * radius * cos(angle),
center.y + scale * radius * sin(angle),
Paint().apply {
this.color = labelColor.toArgb()
this.textSize = size.width / 30
this.textAlign = Paint.Align.CENTER
}
)
}
}
@Preview(showBackground = true)
@Composable
private fun RadarChartPreview() {
RadarChart(
dataCollection = ChartDataCollection(
listOf(
RadarData(30f, "AAAAAA"),
RadarData(25f, "BBBBBB"),
RadarData(20f, "CCCCCC"),
RadarData(15f, "DDDDDD"),
RadarData(10f, "EEEEEE"),
)
),
modifier = Modifier.size(350.dp),
axisConfig = ChartDefaults.axisConfigDefaults(),
radarConfig = RadarChartDefaults.defaultConfig().copy(
fillPolygon = true
)
)
}
@@ -0,0 +1,18 @@
package com.himanshoe.charty.radar.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Represents the colors used in a radar chart.
*
* @property lineColor The colors of the lines in the chart.
* @property dotColor The colors of the dots in the chart.
* @property fillColor The fill color of the polygon in the chart.
*/
@Immutable
data class RadarChartColors(
val lineColor: List<Color>,
val dotColor: List<Color>,
val fillColor: List<Color> = lineColor.map { it.copy(alpha = 0.5F) }
)
@@ -0,0 +1,38 @@
package com.himanshoe.charty.radar.config
import androidx.compose.ui.graphics.Color
/**
* Default configurations and colors for a radar chart.
*/
object RadarChartDefaults {
/**
* Returns the default configuration for a radar chart.
*
* @return The default radar chart configuration.
*/
fun defaultConfig(): RadarConfig =
RadarConfig(
hasDotMarker = true,
strokeSize = 5F,
fillPolygon = true,
)
/**
* Returns the default colors for a radar chart.
*
* @return The default radar chart colors.
*/
fun defaultColor(): RadarChartColors =
RadarChartColors(
lineColor = listOf(
Color(0xffed625d),
Color(0xfff79f88)
),
dotColor = listOf(
Color(0xff50c0a8),
Color(0xff7a57e3),
),
)
}
@@ -0,0 +1,17 @@
package com.himanshoe.charty.radar.config
import androidx.compose.runtime.Immutable
/**
* Configuration options for a data polygon in a radar chart.
*
* @property hasDotMarker Whether the polygon should have a dot marker at each data point.
* @property strokeSize The size of the line stroke.
* @property fillPolygon Whether the polygon should be filled with a color.
*/
@Immutable
data class RadarConfig(
val hasDotMarker: Boolean,
val strokeSize: Float,
val fillPolygon: Boolean,
)
@@ -0,0 +1,19 @@
package com.himanshoe.charty.radar.model
import androidx.compose.runtime.Immutable
import com.himanshoe.charty.common.ChartData
/**
* Represents a data point for a radar chart.
*
* @property yValue The value of the data point.
* @property xValue The label or identifier of the data point.
*/
@Immutable
data class RadarData(
override val yValue: Float,
override val xValue: Any,
) : ChartData {
override val chartString: String
get() = "Radar Chart"
}
@@ -0,0 +1,180 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.stacked
import android.graphics.Paint
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import com.himanshoe.charty.common.ChartSurface
import com.himanshoe.charty.common.ComposeList
import com.himanshoe.charty.common.config.AxisConfig
import com.himanshoe.charty.common.config.ChartDefaults
import com.himanshoe.charty.common.config.ChartyLabelTextConfig
import com.himanshoe.charty.common.toComposeList
import com.himanshoe.charty.common.ui.drawGridLines
import com.himanshoe.charty.common.ui.drawXAxis
import com.himanshoe.charty.common.ui.drawYAxis
import com.himanshoe.charty.stacked.config.StackBarData
/**
* Composable function that renders a stacked bar chart.
*
* @param stackBarData The collection of stack bar data.
* @param modifier The modifier for the chart.
* @param axisConfig The configuration for the chart axes.
* @param padding The padding around the chart.
* @param spacing The spacing between stack bars.
* @param textLabelTextConfig The configuration for the text labels in the chart.
*/
@Composable
fun StackedBarChart(
stackBarData: ComposeList<StackBarData>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
padding: Dp = 16.dp,
spacing: Dp = 4.dp,
textLabelTextConfig: ChartyLabelTextConfig = ChartDefaults.defaultTextLabelConfig(),
) {
val allDataPoints = stackBarData.data.map {
it.dataPoints.sum()
}.toMutableList().apply {
add(0F)
}
val maxSum = stackBarData.data.maxOfOrNull {
it.dataPoints.sum()
} ?: 0f
var chartWidth by remember { mutableStateOf(0f) }
var chartHeight by remember { mutableStateOf(0f) }
ChartSurface(
modifier = modifier,
padding = padding,
axisConfig = axisConfig,
chartData = allDataPoints.toComposeList()
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { size ->
chartWidth = size.width.toFloat()
chartHeight = size.height.toFloat()
}
.drawBehind {
if (axisConfig.showAxes) {
drawYAxis(axisConfig.axisColor, axisConfig.axisStroke)
drawXAxis(axisConfig.axisColor, axisConfig.axisStroke)
}
if (axisConfig.showGridLines) {
drawGridLines(chartWidth, chartHeight, padding.toPx())
}
}
) {
val width = chartWidth - (padding).toPx()
val height = chartHeight - (padding).toPx()
val scaleFactor = chartHeight / maxSum
val barCount = stackBarData.data.size
val totalSpacing = (barCount - 1) * spacing.toPx()
val barWidth = (width - totalSpacing) / barCount.toFloat()
stackBarData.data.fastForEachIndexed { index, stackValues ->
val x = (index * (barWidth + spacing.toPx()))
var startY = height + padding.toPx()
if (axisConfig.showGridLabel) {
when {
barCount < 14 -> {
drawLabel(
stackValues = stackValues,
index = index,
barWidth = barWidth + spacing.toPx(),
chartHeight = chartHeight,
textLabelTextConfig = textLabelTextConfig
)
}
index == 0 || index == barCount / 2 || index == barCount - 1 -> {
drawLabel(
stackValues = stackValues,
index = index,
barWidth = barWidth + spacing.toPx(),
chartHeight = chartHeight,
textLabelTextConfig = textLabelTextConfig
)
}
}
}
stackValues.dataPoints.fastForEachIndexed { stackIndex, value ->
val stackHeight = value * scaleFactor
val endY = startY - stackHeight
val clampedEndY = startY.coerceAtMost(endY)
val clampedStackHeight = startY - clampedEndY
drawRect(
color = stackValues.colors.getOrElse(stackIndex) { Color.Gray },
topLeft = Offset(x, clampedEndY),
size = Size(barWidth, clampedStackHeight)
)
startY = endY
}
}
}
}
}
private fun DrawScope.drawLabel(
stackValues: StackBarData,
index: Int,
barWidth: Float,
chartHeight: Float,
textLabelTextConfig: ChartyLabelTextConfig
) {
val label = stackValues.label
val labelY = chartHeight + size.width / 20
val labelX = when (index) {
0 -> 0.5f * barWidth
else -> (index * barWidth) + (barWidth / 2f)
}
drawIntoCanvas {
it.nativeCanvas.drawText(
label,
labelX,
labelY,
Paint().apply {
color = textLabelTextConfig.textColor.toArgb()
textAlign = Paint.Align.CENTER
textSize = size.width / 35f
}
)
}
}
@@ -0,0 +1,26 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.stacked.config
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/**
* Represents the data for a stack bar chart.
*
* @param label The label associated with the data.
* @param dataPoints The list of data points for each stack.
* @param colors The list of colors for each stack (default is transparent for each stack).
*/
@Immutable
data class StackBarData(
val label: String,
val dataPoints: List<Float>,
val colors: List<Color> = List(dataPoints.count()) { Color.Transparent }
)
@@ -0,0 +1,19 @@
/*
* **************
* Charty Library : Android
*
* Copyright (c) 2023. Charty Contributor
* **************
*/
package com.himanshoe.charty.util
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
@Composable
fun Dp.dpToPx() = with(LocalDensity.current) { this@dpToPx.toPx() }
@Composable
fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }
+45
View File
@@ -0,0 +1,45 @@
### AreaChart
To use the AreaChart, follow the steps below:
- Include the Charty library in your Android project.
- Use the `AreaChart` composable in your code:
```kotlin @Composable
fun AreaChart(
areaData: ComposeList<AreaData>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
padding: Dp = 16.dp,
) {
// Implementation details...
}
```
### Parameters
`AreaChart` accepts the following parameters:
- `areaData`: A `ComposeList` object representing the data to be displayed in area
chart.
- `modifier`: Optional `Modifier` to customize the appearance and behavior of the chart.
- `padding`: Optional `Dp` value representing the padding around the chart. Default is `16.dp`.
- `axisConfig`: Optional `AxisConfig` object representing the configuration of the chart axes.
Default is `ChartDefaults.axisConfigDefaults()`.
Where, AxisConfig looks like,
```kotlin
data class AxisConfig(
val showAxes: Boolean,
val showGridLines: Boolean,
val showGridLabel: Boolean,
val axisStroke: Float,
val minLabelCount: Int,
val axisColor: Color,
val gridColor: Color = axisColor.copy(alpha = 0.5F),
)
```
#### Copyright (c) 2023. Charty Contributor
+70
View File
@@ -0,0 +1,70 @@
### BarChart
To use the BarChart, follow the steps below:
- Include the Charty library in your Android project.
- Use the `BarChart` composable in your code:
```kotlin @Composable
fun BarChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
barSpacing: Dp = 8.dp,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
) {
// Implementation details...
}
```
or
```kotlin @Composable
fun BarChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
barSpacing: Dp = 8.dp,
padding: Dp = 16.dp,
barColor: Color = Color.Blue,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
){
// Implementation details...
}
```
In the above `BarChart`, we have `barColor` that will override the individual BarData's `color`
### Parameters
`BarChart` accepts the following parameters:
- `dataCollection`: A `ChartDataCollection` object representing the data to be displayed in the bar
chart.
- `modifier`: Optional `Modifier` to customize the appearance and behavior of the chart.
- `barSpacing`: Optional `Dp` value representing the spacing between bars in the chart. Default
is `8.dp`.
- `padding`: Optional `Dp` value representing the padding around the chart. Default is `16.dp`.
- `barColor`: Optional `Color` value representing the color of the bars in the chart. Default
is `Color.Blue`.
- `axisConfig`: Optional `AxisConfig` object representing the configuration of the chart axes.
Default is `ChartDefaults.axisConfigDefaults()`.
Where, AxisConfig looks like,
```kotlin
data class AxisConfig(
val showAxes: Boolean,
val showGridLines: Boolean,
val showGridLabel: Boolean,
val axisStroke: Float,
val minLabelCount: Int,
val axisColor: Color,
val gridColor: Color = axisColor.copy(alpha = 0.5F),
)
```
#### Copyright (c) 2023. Charty Contributor
+56
View File
@@ -0,0 +1,56 @@
### BubbleChart
To use the BubbleChart, follow the steps below:
- Include the Charty library in your Android project.
- Use the `BubbleChart` composable in your code:
```kotlin @Composable
fun BubbleChart(
dataCollection: ChartDataCollection,
modifier: Modifier = Modifier,
padding: Dp = 16.dp,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
chartColors: CurvedLineChartColors = CurvedLineChartDefaults.defaultColor(),
) {
// Implementation details...
}
```
### Parameters
`BubbleChart` accepts the following parameters:
- `dataCollection`: A `ChartDataCollection` object representing the data to be displayed in bubble
chart of type `BubbleData`.
- `modifier`: Optional `Modifier` to customize the appearance and behavior of the chart.
- `padding`: Optional `Dp` value representing the padding around the chart. Default is `16.dp`.
- `chartColors`: Optional `CurvedLineChartColors` value representing the color used in the chart.
where it looks like,
```kotlin
data class CurvedLineChartColors(
val dotColor: List<Color> = emptyList(),
val backgroundColors: List<Color> = emptyList(),
val contentColor: List<Color> = emptyList(),
)
```
- `axisConfig`: Optional `AxisConfig` object representing the configuration of the chart axes.
Default is `ChartDefaults.axisConfigDefaults()`.
Where, AxisConfig looks like,
```kotlin
data class AxisConfig(
val showAxes: Boolean,
val showGridLines: Boolean,
val showGridLabel: Boolean,
val axisStroke: Float,
val minLabelCount: Int,
val axisColor: Color,
val gridColor: Color = axisColor.copy(alpha = 0.5F),
)
```
#### Copyright (c) 2023. Charty Contributor
+59
View File
@@ -0,0 +1,59 @@
### CandleStickChart
To use the CandleStickChart, follow the steps below:
- Include the Charty library in your Android project.
- Use the `CandleStickChart` composable in your code:
```kotlin @Composable
fun CandleStickChart(
candleData: ComposeList<CandleData>,
modifier: Modifier = Modifier,
axisConfig: AxisConfig = ChartDefaults.axisConfigDefaults(),
padding: Dp = 16.dp,
candleConfig: CandleStickConfig = CandleStickDefaults.defaultCandleStickConfig(),
) {
// Implementation details...
}
```
### Parameters
`CandleStickChart` accepts the following parameters:
- `candleData`: A `ComposeList` object representing the data to be displayed in CandleStickChart
of type `CandleData`.
- `modifier`: Optional `Modifier` to customize the appearance and behavior of the chart.
- `padding`: Optional `Dp` value representing the padding around the chart. Default is `16.dp`.
- `candleConfig`: Optional `CandleStickConfig` value representing the config used in the chart.
where it looks like,
```kotlin
@Immutable
data class CandleStickConfig(
val positiveColor: Color,
val negativeColor: Color,
val wickColor: Color,
val canCandleScale: Boolean,
val wickWidthScale: Float = 0.05f,
)
```
- `axisConfig`: Optional `AxisConfig` object representing the configuration of the chart axes.
Default is `ChartDefaults.axisConfigDefaults()`.
Where, `AxisConfig` looks like,
```kotlin
data class AxisConfig(
val showAxes: Boolean,
val showGridLines: Boolean,
val showGridLabel: Boolean,
val axisStroke: Float,
val minLabelCount: Int,
val axisColor: Color,
val gridColor: Color = axisColor.copy(alpha = 0.5F),
)
```
#### Copyright (c) 2023. Charty Contributor

Some files were not shown because too many files have changed in this diff Show More