的
@@ -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
|
||||
@@ -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
|
||||
@@ -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!
|
||||
@@ -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.
|
||||
@@ -0,0 +1,67 @@
|
||||
## Charty : Elementary Chart library for Compose
|
||||
|
||||

|
||||
|
||||
Chart Library built using Jetpack Compose and is highly customizable.
|
||||
_Made with ❤️ for Android Developers by Himanshu_
|
||||
|
||||
[](https://github.com/hi-manshu)
|
||||
[](https://twitter.com/hi_man_shoe)
|
||||
[](https://androidweekly.net/issues/issue-532)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
@@ -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() }
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||