feat: Origin

This commit is contained in:
2023-01-10 12:45:36 +01:00
commit 4d787d9393
91 changed files with 3371 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

View File

@ -0,0 +1,2 @@
#Tue Jan 10 11:53:18 CET 2023
gradle.version=7.5.1

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>

View File

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
Dzeio Charts

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

19
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/library" />
<option value="$PROJECT_DIR$/sample" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.0" />
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="TestModuleProperties" production-module="Crash_Handler.library.main" />
</module>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="TestModuleProperties" production-module="Crash_Handler.library.main" />
</module>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="TestModuleProperties" production-module="Crash_Handler.library.main" />
</module>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="TestModuleProperties" production-module="Crash_Handler.sample.main" />
</module>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="TestModuleProperties" production-module="Crash_Handler.sample.main" />
</module>

31
.idea/navEditor.xml generated Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="navEditor-manualLayoutAlgorithm2">
<option name="myPositions">
<map>
<entry key="nav_graph.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="MainFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="12" />
<option name="y" value="12" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible 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.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders 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, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
the `contact@dze.io` email or on Discord to `Avior#3872`.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

174
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,174 @@
<!-- omit in toc -->
# Contributing to Crash handler
First off, thanks for taking the time to contribute! ❤
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
>
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
<!-- omit in toc -->
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Issues](#reporting-issues)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Styleguides](#styleguides)
- [Coding Guidlines](#coding-guidelines)
- [Commit Messages](#commit-messages)
- [Join The Project Team](#join-the-project-team)
## Code of Conduct
This project and everyone participating in it is governed by the
[DZEIO Code of Conduct](https://github.com/dzeiocom/crashhandler/blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to <contact.crashhandler@dze.io>.
## I Have a Question
You can ask them on the Github Repository, it is best to search for existing [Issues](https://github.com/dzeiocom/crashhandler/issues) that might help you.
In case you have found a suitable issue and still need clarification, you can write your question in a new issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](https://github.com/dzeiocom/crashhandler/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (Android Version, Application Version), depending on what seems relevant.
We will then take care of the issue as soon as possible.
<!--
You might want to create a separate issue tag for questions and include it in this description. People should then tag their issues accordingly.
Depending on how large the project is, you may want to outsource the questioning, e.g. to Stack Overflow or Gitter. You may add additional contact and information possibilities:
- IRC
- Slack
- Gitter
- Stack Overflow tag
- Blog
- FAQ
- Roadmap
- E-Mail List
- Forum
-->
## I Want To Contribute
### Reporting Issues
#### Before Submitting an Issue Report
A good issue report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Determine if your issue is not an error on your side e.g. using incompatible environment components/versions. If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already an issue report existing for your bug or error in the [bug tracker](https://github.com/dzeiocom/crashhandler/issues).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Phone, OS Version, App Version, etc...)
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
<!-- omit in toc -->
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <security@dze.io>.
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](https://github.com/dzeiocom/crashhandler/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it's filled:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
<!-- You might want to create an issue template for bugs and errors that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
<!-- omit in toc -->
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Perform a [search](https://github.com/dzeiocom/crashhandler/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
<!-- omit in toc -->
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://github.com/dzeiocom/crashhandler/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to.
- **Explain why this enhancement would be useful**. You may also want to point out the other projects that solved it better and which could serve as inspiration.
<!-- You might want to create an issue template for enhancement suggestions that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
### Your First Code Contribution
_note: Follow the different styleguides listed below when contributing_
- Fork 🍴 the project. _see the `fork` button at the top right of the screen_
- make the changes you want in your repository.
- Create a Pull request here https://github.com/dzeiocom/crashhandler/compare by selecting your repository patch with our `master` branch _If it's not finished put WIP: before_
- we don't like ❌, so if your pull request has its automated checks ending with the red cross, please double check your changes until it show the awesome 🟢, or ask for help !
- If your pull request is ready for review remove WIP: put it s ready for review and we will handle the rest !
## Styleguides
### Coding Guidelines
Install the plugin `.editorconfig` in your dev environment if it's not already done and thats it !
### Commit Messages
We follow the [Conventional Commit specification](conventionalcommits.org) and are using the [Angular format](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit) with some changes.
In short, please name your Pull Requests/Commits following this format
```
<type>(<scope>): <short summary>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```
- `type` MUST be one of
- build: Changes affecting the CI at `.github/workflows` or `Dockerfile`
- ci: same as build
- docs: Changes affecting the differents Documents
- feat: a new feature being added
- fix: fixes
- pref: Performance related fixes
- refactor: Misc code changes
- test: same as `ci`
- the field `(<scope>)` is optional
- `short summary` MUST follow thoses rules
- Capitalize the first letter
- use the imperative, present tense: "Change" not "Changed" nor "Changes"
- `<footer>` and `<body>` are both optional
## Attribution
This guide is based on [Contribution Gen]((https://github.com/bttger/contributing-gen)) and was adapted by the community !

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Florian Bouillon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# Crash Handler
Highly customizable and easy to use Chart library for android
## Install
- Add Jitpack.io to your `settings.gradle` file `maven { url 'https://jitpack.io' }`
Add to you dependencies (check the latest release for the version):
- (Gradle Kotlin DSL) Add `implementation("com.dzeio:charts:1.0.0")`
- (Gradle Groovy DSL) Add `implementation "com.dzeio:charts:1.0.0" `
## Build
- Install Android Studio
- Build the app
- it will be running on your emulator/device
- test it!
## Contributing
See [CONTRIBUTING.md](https://github.com/dzeiocom/charts/blob/master/CONTRIBUTING.md)
TL::DR
- Fork
- Commit your changes
- Pull Request on this Repository
## License
This project is licensed under the MIT License. A copy of the license is available at [LICENSE.md](https://github.com/dzeiocom/charts/blob/master/LICENSE.md)

23
build.gradle.kts Normal file
View File

@ -0,0 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id("com.android.application") version "7.3.1" apply false
id("com.android.library") version "7.3.1" apply false
id("org.jetbrains.kotlin.android") version "1.7.0" apply false
}
task("clean") {
delete(rootProject.buildDir)
delete(project.buildDir)
}

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
#Tue Jul 19 18:42:00 CEST 2022
android.nonTransitiveRClass=true
kotlin.code.style=official
android.useAndroidX=true
android.enableJetifier=false
org.gradle.unsafe.configuration-cache=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sun Aug 07 22:38:24 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
library/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

34
library/build.gradle.kts Normal file
View File

@ -0,0 +1,34 @@
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdk = 33
defaultConfig {
minSdk = 21
targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "com.dzeio.charts"
}

View File

21
library/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.dzeio.charts
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.dzeio.charts.test", appContext.packageName)
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest></manifest>

View File

@ -0,0 +1,66 @@
package com.dzeio.charts
import kotlin.math.abs
import kotlin.math.max
data class Animation(
/**
* Enable / Disable the Chart Animations
*/
var enabled: Boolean = true,
/**
* Number of milliseconds the animation is running before it ends
*/
var duration: Int = 1000,
/**
* Number of updates per seconds
*/
var refreshRate: Int = 50
) {
/**
* Update the value depending on the maximum obtainable value
*
* @param maxValue the maximum value the item can obtain
* @param targetValue the value you want to obtain at the end of the animation
* @param currentValue the current value
*
* @return the new updated value
*/
fun updateValue(
maxValue: Float,
targetValue: Float,
currentValue: Float,
minValue: Float,
minStep: Float
): Float {
if (!enabled) {
return targetValue
}
if (currentValue < minValue) {
return minValue
}
val moveValue = max(minStep, (maxValue - targetValue) / refreshRate)
var result = targetValue
if (currentValue < targetValue) {
result = currentValue + moveValue
} else if (currentValue > targetValue) {
result = currentValue - moveValue
}
if (
abs(targetValue - currentValue) <= moveValue ||
result < minValue ||
result > maxValue
) {
return targetValue
}
return result
}
fun getDelay() = this.duration / this.refreshRate
}

View File

@ -0,0 +1,150 @@
package com.dzeio.charts
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.components.ChartScroll
import com.dzeio.charts.series.SerieInterface
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
View(context, attrs), ChartViewInterface {
private companion object {
const val TAG = "Charts/ChartView"
}
override var debug: Boolean = false
override val xAxis = XAxis(this)
override val yAxis = YAxis(this)
override var series: ArrayList<SerieInterface> = arrayListOf()
override var padding: Float = 8f
private val scroller = ChartScroll(this).apply {
var lastMovement = 0.0
setOnChartMoved { movementX, _ ->
xAxis.x += (movementX - lastMovement) * xAxis.getDataWidth() / width
lastMovement = movementX.toDouble()
refresh()
}
// setOnZoomChanged {
// Log.d(TAG, "New Zoom: $it")
// zoom = (it * 1.2).toFloat()
// refresh()
// }
}
// val animator: Runnable = object : Runnable {
// override fun run() {
// var needNewFrame = false
// for (serie in series) {
// val result = serie.onUpdate()
// if (result) {
// needNewFrame = true
// }
// }
// if (needNewFrame) {
// postDelayed(this, animation.getDelay().toLong())
// }
// invalidate()
// }
// }
// rect used for calculations
private val rect = RectF()
// stroke used while in debug
private val debugStrokePaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 8f
color = Color.parseColor("#654321")
}
override fun refresh() {
// run Axis logics
xAxis.refresh()
yAxis.refresh()
// run series logic
for (serie in series) {
serie.refresh()
}
// invalidate the view
invalidate()
// removeCallbacks(animator)
// post(animator)
}
override fun onDraw(canvas: Canvas) {
// don't draw anything if everything is empty
if (series.isEmpty() || series.maxOf { it.entries.size } == 0) {
super.onDraw(canvas)
return
}
if (debug) {
// draw corners
canvas.drawRect(rect.apply {
set(
padding / 2,
padding / 2,
width.toFloat() - padding / 2,
height.toFloat() - padding / 2
)
}, debugStrokePaint)
}
val bottom = xAxis.onDraw(canvas, rect.apply {
set(padding, 0f, width.toFloat() - padding, height.toFloat() - padding)
})
// right distance from the yAxis
val rightDistance = yAxis.onDraw(canvas, rect.apply {
set(padding, padding, width.toFloat() - padding, height.toFloat() - bottom - padding)
})
// chart draw rectangle
rect.apply {
set(
padding,
padding,
width.toFloat() - padding - rightDistance,
height - bottom - padding
)
}
for (serie in series) {
serie.onDraw(canvas, rect)
}
super.onDraw(canvas)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
performClick()
return scroller.onTouchEvent(event)
}
override fun getDataset(): ArrayList<Entry> {
val data: ArrayList<Entry> = arrayListOf()
for (serie in series) {
data.addAll(serie.entries)
}
data.sortBy { it.x }
return data.filterIndexed { index, entry -> data.indexOf(entry) == index } as ArrayList<Entry>
}
}

View File

@ -0,0 +1,47 @@
package com.dzeio.charts
import com.dzeio.charts.axis.XAxisInterface
import com.dzeio.charts.axis.YAxisInterface
import com.dzeio.charts.series.SerieInterface
interface ChartViewInterface {
/**
* Make the whole view in debug mode
*
* add debug texts, logs, and more
*/
var debug: Boolean
/**
* the padding inside the view
*/
var padding: Float
/**
* Hold metadata about the X axis
*/
val xAxis: XAxisInterface
/**
* Hold informations about the Y axis
*/
val yAxis: YAxisInterface
/**
* handle the series
*/
var series: ArrayList<SerieInterface>
/**
* refresh and run pre-display logic the chart
*
* this function should be run if you change parameters in the view
*/
fun refresh()
/**
* @return the whole dataset (sorted and cleaned up of dupps)
*/
fun getDataset(): ArrayList<Entry>
}

View File

@ -0,0 +1,10 @@
package com.dzeio.charts
/**
* A Base entry for any charts
*/
data class Entry(
var x: Double,
var y: Float,
var color: Int? = null
)

View File

@ -0,0 +1,140 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.Log
import com.dzeio.charts.ChartViewInterface
import com.dzeio.charts.Entry
import kotlin.math.roundToInt
class XAxis(
private val view: ChartViewInterface
) : XAxisInterface {
private companion object {
const val TAG = "Charts/XAxis"
}
override var x: Double = 0.0
set(value) {
val max = getXMax() - getDataWidth()
val min = getXMin()
if (value > max && min <= max) {
field = max
return
}
if (value < min) {
field = min
return
}
field = value
}
override var enabled = true
override var dataWidth: Double? = null
get() = field ?: getXMax()
override var labelCount: Int = 2
var spacing = 16.0
override val textPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#FC496D")
textSize = 30f
textAlign = Paint.Align.LEFT
}
private val rect = Rect()
override fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Double {
return translatePositionToRect(entry.x, drawableSpace)
}
fun translatePositionToRect(value: Double, drawableSpace: RectF): Double {
return drawableSpace.width() * (value - x) / getDataWidth()
}
override fun getXMax(): Double {
return view.series.maxOf { serie ->
if (serie.entries.isEmpty()) {
return 0.0
}
serie.entries.maxOf { entry -> entry.x }
}
}
override fun getXMin(): Double {
return view.series.minOf { serie ->
if (serie.entries.isEmpty()) {
return 0.0
}
serie.entries.minOf { entry -> entry.x }
}
}
var onValueFormat: (value: Double) -> String = { it -> it.roundToInt().toString() }
override fun onDraw(canvas: Canvas, space: RectF): Float {
if (!enabled) {
return 0f
}
var maxHeight = 0f
val graphIncrement = space.width() / (labelCount - 1)
val valueIncrement = (getDataWidth() / (labelCount - 1)).toDouble()
for (index in 0 until labelCount) {
val text = onValueFormat(x + valueIncrement * index)
textPaint.getTextBounds(text, 0, text.length, rect)
maxHeight = maxHeight.coerceAtLeast(rect.height().toFloat() + 1)
var xPos = space.left + graphIncrement * index
if (xPos + rect.width() > space.right) {
xPos = space.right - rect.width()
}
canvas.drawText(
text,
xPos,
space.bottom,
textPaint
)
}
return maxHeight + 32f
}
override fun refresh() {
// TODO("Not yet implemented")
}
override fun getEntryWidth(drawableSpace: RectF): Double {
var smallest = Double.MAX_VALUE
val dataset = view.getDataset()
for (idx in 0 until dataset.size - 1) {
val distance = dataset[idx + 1].x - dataset[idx].x
if (smallest > distance && distance > 0.0) {
smallest = distance
}
}
return clamp(drawableSpace.width() * smallest / getDataWidth() - spacing, 1.0, drawableSpace.width().toDouble())
}
override fun getDataWidth(): Double {
// TODO: handle the auto dataWidth better
return dataWidth ?: getXMax()
}
private fun clamp(value: Double, min: Double, max: Double): Double {
return if (value < min) min else if (value > max) max else value
}
}

View File

@ -0,0 +1,84 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import com.dzeio.charts.Entry
sealed interface XAxisInterface {
/**
* enable/disable the display of the xAxis
*/
var enabled: Boolean
/**
* set X position
*/
var x: Double
/**
* the "width" of the graph
*
* if not set it will be `XMax - XMin`
*
* ex: to display a 7 days graph history with x values being timestamp in secs, use 7*24*60*60
*/
var dataWidth: Double?
/**
* text Paint
*/
val textPaint: Paint
/**
* indicate the number of labels displayed
*/
var labelCount: Int
/**
* run when manually refreshing the system
*
* this is where the pre-logic is handled to make [onDraw] quicker
*/
fun refresh()
/**
* get the entry position on the rect
*
* @return the left side of the position of the entry
*/
fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Double
/**
* get the maximum the X can get to
*/
fun getXMax(): Double
/**
* get the minimum the X can get to
*/
fun getXMin(): Double
/**
* get the size of an entry in the graph
*
* @return the size in [drawableSpace] px
*/
fun getEntryWidth(drawableSpace: RectF): Double
/**
* return the currently used dataWidth
*/
fun getDataWidth(): Double
/**
* onDraw event that will draw the XAxis
*
* @param canvas the canvas to draw on
* @param space the space where it is allowed to draw
*
* @return the final height of the XAxis
*/
fun onDraw(canvas: Canvas, space: RectF): Float
}

View File

@ -0,0 +1,146 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import com.dzeio.charts.ChartViewInterface
import com.dzeio.charts.utils.drawDottedLine
import kotlin.math.roundToInt
class YAxis(
private val view: ChartViewInterface
) : YAxisInterface {
override var enabled = true
override val textLabel = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#FC496D")
textSize = 30f
textAlign = Paint.Align.LEFT
}
override val linePaint = Paint().apply {
isAntiAlias = true
color = Color.BLUE
}
override val goalLinePaint = Paint().apply {
isAntiAlias = true
color = Color.RED
strokeWidth = 4f
}
var onValueFormat: (value: Float) -> String = { it -> it.roundToInt().toString() }
override var labelCount = 5
private var min: Float? = 0f
private var max: Float? = null
private val rect = Rect()
override fun setYMin(yMin: Float?): YAxisInterface {
min = yMin
return this
}
override fun setYMax(yMax: Float?): YAxisInterface {
max = yMax
return this
}
override fun getYMax(): Float {
if (max != null) {
return max!!
}
if (view.series.isEmpty()) {
return (this.goalLine ?: 90f) + 10f
}
val seriesMax = view.series
.maxOf { serie ->
if (serie.getDisplayedEntries().isEmpty()) {
return@maxOf 0f
}
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
}
if (this.goalLine != null) {
return if (seriesMax > this.goalLine!!) seriesMax else this.goalLine!! + 1000f
}
return seriesMax
}
override fun getYMin(): Float {
if (min != null) {
return min!!
}
if (view.series.isEmpty()) {
return this.goalLine ?: 0f
}
return view.series
.minOf { serie ->
if (serie.getDisplayedEntries().isEmpty()) {
return@minOf 0f
}
return@minOf serie.getDisplayedEntries().minOf { entry -> entry.y }
}
}
override fun onDraw(canvas: Canvas, space: RectF): Float {
if (!enabled) {
return 0f
}
val min = getYMin()
val max = getYMax() - min
val top = space.top
val bottom = space.bottom
var maxWidth = 0f
val increment = (bottom - top) / labelCount
val valueIncrement = (max - min) / labelCount
for (index in 0 until labelCount) {
val text = onValueFormat((valueIncrement * (index + 1)))
textLabel.getTextBounds(text, 0, text.length, rect)
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
val posY = bottom - (index + 1) * increment
canvas.drawText(
text,
space.width() - rect.width().toFloat(),
(posY + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
textLabel
)
// canvas.drawDottedLine(0f, posY, canvas.width.toFloat(), posY, 40f, linePaint)
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
}
if (this.goalLine != null) {
val pos = (1 - this.goalLine!! / max) * space.height() + space.top
canvas.drawDottedLine(
0f,
pos,
space.right - maxWidth - 32f,
pos,
space.right / 20,
goalLinePaint
)
}
return maxWidth + 32f
}
override fun refresh() {
// TODO("Not yet implemented")
}
private var goalLine: Float? = null
override fun setGoalLine(height: Float?) {
goalLine = height
}
}

View File

@ -0,0 +1,85 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
sealed interface YAxisInterface {
/**
* whether or not this axis is displayed
*/
var enabled: Boolean
/**
* get/set the number of label of this Y axis
*
* the first/last labels are at the bottom/top of the chart
*/
var labelCount: Int
/**
* text label paint
*/
val textLabel: Paint
/**
* paint for the lines
*/
val linePaint: Paint
/**
* Goal line paint
*/
val goalLinePaint: Paint
/**
* run when manually refreshing the system
*
* this is where the pre-logic is handled to make [onDraw] quicker
*/
fun refresh()
/**
* override Y minimum
*
* @param yMin is set the min will ba at the value, if null it is calculated
*/
fun setYMin(yMin: Float?): YAxisInterface
/**
* override Y maximum
*
* @param yMax is set the max will ba at the value, if null it is calculated
*/
fun setYMax(yMax: Float?): YAxisInterface
/**
* get Y maximum
*
* @return the maximum value Y can get (for displayed values)
*/
fun getYMax(): Float
/**
* get Y minimum
*
* @return the minimum value Y can get (for displayed values)
*/
fun getYMin(): Float
/**
* function that draw our legend
*
* @param canvas the canvas to draw on
* @param space the space where it is allowed to draw on
*
* @return the width of the sidebar
*/
fun onDraw(canvas: Canvas, space: RectF): Float
/**
* Add a Goal line
*/
fun setGoalLine(height: Float?)
}

View File

@ -0,0 +1,11 @@
package com.dzeio.charts.axis
/**
* CURRENTLY UNUSED
*
* declare where the YAxis for the graph will be
*/
enum class YAxisPosition {
LEFT,
RIGHT
}

View File

@ -0,0 +1,144 @@
package com.dzeio.charts.components
import android.view.MotionEvent
import android.view.MotionEvent.INVALID_POINTER_ID
import android.view.ScaleGestureDetector
import android.view.View
/**
* Class handling the scroll/zoom for the library
*/
class ChartScroll(view: View) {
/**
* Enabled the zoom/unzoom of datas
*/
var zoomEnabled = true
/**
* Enable the horizontal scroll feature
*/
var scrollEnabled = true
// The active pointer is the one currently moving our object.
private var activePointerId = INVALID_POINTER_ID
private var lastTouchX: Float = 0f
private var lastTouchY: Float = 0f
private var posX: Float = 0f
private var posY: Float = 0f
private var lastZoom: Float = 100f
private var currentZoom: Float = 0f
private var onChartMoved: ((movementX: Float, movementY: Float) -> Unit)? = null
fun setOnChartMoved(fn: (movementX: Float, movementY: Float) -> Unit) {
onChartMoved = fn
}
private var onZoomChanged: ((scale: Float) -> Unit)? = null
/**
* @param fn.scale Float starting from 100%
*
* 99-% zoom out,
* 101+% zoom in
*/
fun setOnZoomChanged(fn: (scale: Float) -> Unit) {
onZoomChanged = fn
}
private val scaleGestureDetector = ScaleGestureDetector(
view.context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
if (currentZoom != detector.scaleFactor) {
currentZoom = detector.scaleFactor
onZoomChanged?.invoke(lastZoom + -currentZoom + 1)
}
return super.onScale(detector)
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
super.onScaleEnd(detector)
lastZoom += -currentZoom + 1
}
}
)
/**
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
if (zoomEnabled) {
scaleGestureDetector.onTouchEvent(ev)
}
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
onToggleScroll?.invoke(false)
ev.actionIndex.also { pointerIndex ->
// Remember where we started (for dragging)
lastTouchX = ev.getX(pointerIndex)
lastTouchY = ev.getY(pointerIndex)
}
// Save the ID of this pointer (for dragging)
activePointerId = ev.getPointerId(0)
}
MotionEvent.ACTION_MOVE -> {
// Find the index of the active pointer and fetch its position
val (x: Float, y: Float) =
ev.findPointerIndex(activePointerId).let { pointerIndex ->
// Calculate the distance moved
ev.getX(pointerIndex) to ev.getY(pointerIndex)
}
posX += x - lastTouchX
posY += y - lastTouchY
if (scrollEnabled) {
onChartMoved?.invoke(-posX, posY)
}
// Remember this touch position for the next move event
lastTouchX = x
lastTouchY = y
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
onToggleScroll?.invoke(true)
activePointerId = INVALID_POINTER_ID
}
MotionEvent.ACTION_POINTER_UP -> {
onToggleScroll?.invoke(true)
ev.actionIndex.also { pointerIndex ->
ev.getPointerId(pointerIndex)
.takeIf { it == activePointerId }
?.run {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
val newPointerIndex = if (pointerIndex == 0) 1 else 0
lastTouchX = ev.getX(newPointerIndex)
lastTouchY = ev.getY(newPointerIndex)
activePointerId = ev.getPointerId(newPointerIndex)
}
}
}
}
return true
}
private var onToggleScroll: ((Boolean) -> Unit)? = null
/**
* @param ev if input is false disable scroll
*/
fun setOnToggleScroll(ev: (Boolean) -> Unit) {
onToggleScroll = ev
}
}

View File

@ -0,0 +1,125 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.Log
import com.dzeio.charts.ChartView
import com.dzeio.charts.utils.drawRoundRect
class BarSerie(
private val view: ChartView
) : BaseSerie(view) {
private companion object {
const val TAG = "Charts/BarSerie"
}
init {
view.series.add(this)
}
val barPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#123456")
}
val textPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#FC496D")
textSize = 30f
textAlign = Paint.Align.CENTER
}
private val rect = Rect()
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
val displayedEntries = getDisplayedEntries()
val barWidth = view.xAxis.getEntryWidth(drawableSpace).toFloat()
val max = view.yAxis.getYMax()
val min = view.yAxis.getYMin()
// Log.d(TAG, "${space.left}, ${space.right}")
for (entry in displayedEntries) {
// calculated height in percent from 0 to 100
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
var posX = drawableSpace.left + view.xAxis.getPositionOnRect(
entry,
drawableSpace
).toFloat()
val right = (posX + barWidth).coerceAtMost(drawableSpace.right)
if (posX > right) {
continue
} else if (posX < drawableSpace.left) {
posX = drawableSpace.left
}
if (right < drawableSpace.left) {
continue
}
// handle color recoloration
val paint = Paint(barPaint)
if (entry.color != null) {
paint.color = entry.color!!
}
canvas.drawRoundRect(
posX,
top,
right,
drawableSpace.bottom,
// 8f, 8f,
32f,
32f,
0f,
0f,
paint
)
// handle text display
val text = view.yAxis.onValueFormat(entry.y)
textPaint.getTextBounds(text, 0, text.length, rect)
val textLeft = (posX + barWidth / 2)
if (
// handle right side
textLeft + rect.width() / 2 > right ||
// handle left sie
textLeft - rect.width() / 2 < drawableSpace.left
) {
continue
}
val doDisplayIn =
rect.height() < drawableSpace.bottom - top &&
rect.width() < barWidth
var textY = if (doDisplayIn) top + rect.height() + 16f else top - 16f
if (textY < 0) {
textY = drawableSpace.top + rect.height()
}
canvas.drawText(
text,
textLeft,
textY,
textPaint
)
}
}
override fun refresh() {
// TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,160 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.Log
import kotlin.math.max
class BarSerie : SerieAbstract() {
companion object {
const val TAG = "DzeioCharts/BarSerie"
}
var spacing: Float = 8f
/**
* Values displayed on the grapd
*/
var displayedDatas = arrayListOf<Float>()
/**
* Target values
*/
var targetDatas = arrayListOf<Float>()
var targetPercentList = arrayListOf<Float>()
var percentList = arrayListOf<Float>()
var previousRefresh = 0
private var fgPaint: Paint = Paint().apply {
isAntiAlias = true
}
private val r = Rect()
override fun onUpdate(): Boolean {
var needNewFrame = false
for (i in targetPercentList.indices) {
val value = view.animation.updateValue(
1f,
targetPercentList[i],
percentList[i],
0f,
0.00f
)
if (value != percentList[i]) {
needNewFrame = true
percentList[i] = value
}
}
return needNewFrame
}
override fun prepareData() {
val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else {
getYMax(true)
}
targetPercentList = arrayListOf()
// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}")
for (item in getDisplayedEntries()) {
// // // Process bottom texts
// val text = view.xAxis.onValueFormat(item.x)
// bottomTexts.add(text)
//
// // get Text boundaries
// view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
//
// // get height of text
// if (bottomTextHeight < r.height()) {
// bottomTextHeight = r.height()
// }
//
// // get text descent
// val descent = abs(r.bottom)
// if (bottomTextDescent < descent) {
// bottomTextDescent = descent
// }
// process values
// Log.d(TAG, item.y.toString())
// add to animations the values
targetPercentList.add(1 - item.y / max)
}
// post list
val offset = view.getXOffset()
val movement = offset - previousRefresh
Log.d(TAG, "$offset - $previousRefresh = $movement")
if (movement != 0) {
previousRefresh = offset
}
// if (movement != 0) {
// Log.d(TAG, movement.toString())
// }
if (movement >= 1) {
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
percentList.add(1f)
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
percentList.add(0, 1f)
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
for (i in 0 until temp) {
percentList.add(1f)
}
} else if (percentList.size > targetPercentList.size) {
val temp = percentList.size - targetPercentList.size
for (i in 0 until temp) {
percentList.removeAt(percentList.size - 1)
}
}
fgPaint.color = view.yAxis.color
}
override fun displayData(canvas: Canvas, rect: RectF) {
val barWidth = (rect.width() - view.padding * 2) / view.getDisplayedEntries() - spacing
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..percentList.size) {
// Log.d(TAG, percentList[i - 1].toString())
val left = rect.left + spacing * i + barWidth * (i - 1).toFloat() + view.padding
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = rect.left + (spacing + barWidth) * i.toFloat()
val bottom = rect.top + rect.height() - view.padding
val top = (bottom - rect.top) * percentList[i - 1] + view.padding
// create rounded rect
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
// remove the bottom corners DUH
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
val targetTop = (bottom - rect.top) * targetPercentList[i - 1]
val text = view.yAxis.onValueFormat(getYMax(true) - getYMax(true) * targetPercentList[i - 1], true)
view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
val doDisplayIn =
r.width() + 10f < barWidth && bottom - targetTop > r.height() + 40f
if (view.debug || !doDisplayIn || (doDisplayIn && bottom - top > r.height() + 40f)) {
val y = if (doDisplayIn) top + r.height() + 20f else top - r.height()
canvas.drawText(
text,
left + (right - left) / 2,
y,
view.xAxis.labels.build()
)
}
}
}
}
}

View File

@ -0,0 +1,50 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.RectF
import com.dzeio.charts.ChartViewInterface
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.YAxisPosition
import kotlin.math.roundToInt
sealed class BaseSerie(
private val view: ChartViewInterface
) : SerieInterface {
private companion object {
const val TAG = "Charts/BaseSerie"
}
override var formatValue: (entry: Entry) -> String = { entry -> entry.y.roundToInt().toString()}
override var yAxisPosition: YAxisPosition = YAxisPosition.RIGHT
override var entries: ArrayList<Entry> = arrayListOf()
override fun getDisplayedEntries(): ArrayList<Entry> {
val minX = view.xAxis.x
val maxX = minX + view.xAxis.getDataWidth()
val result: ArrayList<Entry> = arrayListOf()
var lastIndex = -1
for (i in 0 until entries.size) {
val it = entries[i]
if (it.x in minX..maxX) {
if (result.size === 0 && i > 0) {
result.add((entries[i - 1]))
}
lastIndex = i
result.add(it)
}
}
if (lastIndex < entries.size - 1) {
result.add(entries [lastIndex + 1])
}
return result
}
abstract override fun onDraw(canvas: Canvas, drawableSpace: RectF)
}

View File

@ -0,0 +1,74 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import com.dzeio.charts.ChartView
class LineSerie(
private val view: ChartView
) : BaseSerie(view) {
private companion object {
const val TAG = "Charts/LineSerie"
}
init {
view.series.add(this)
}
val linePaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#123456")
strokeWidth = 5f
}
val textPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#FC496D")
textSize = 30f
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
val displayedEntries = getDisplayedEntries()
displayedEntries.sortBy { it.x }
val max = view.yAxis.getYMax()
var previousPosX: Float? = null
var previousPosY: Float? = null
for (entry in displayedEntries) {
// calculated height in percent from 0 to 100
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
val posX = (drawableSpace.left +
view.xAxis.getPositionOnRect(entry, drawableSpace) +
view.xAxis.getEntryWidth(drawableSpace) / 2f).toFloat()
// handle color recoloration
val paint = Paint(linePaint)
if (entry.color != null) {
paint.color = entry.color!!
}
// draw smol point
if (posX < drawableSpace.right) {
canvas.drawCircle(posX, top, paint.strokeWidth, paint)
}
// draw line
if (previousPosX != null && previousPosY != null) {
canvas.drawLine(previousPosX, previousPosY, posX, top, paint)
}
previousPosX = posX
previousPosY = top
}
}
override fun refresh() {
// TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,47 @@
package com.dzeio.charts.series
import android.graphics.Canvas
import android.graphics.RectF
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.YAxisPosition
sealed interface SerieInterface {
/**
* location of the Y axis
*/
var yAxisPosition: YAxisPosition
/**
* filter out out of display entries
*
* @return the list of entries displayed
*/
fun getDisplayedEntries(): ArrayList<Entry>
/**
* set the entries for the list
*/
var entries: ArrayList<Entry>
/**
* Change how the value is displayed for each elements
*/
var formatValue: (entry: Entry) -> String
/**
* function that display the graph
*
* @param canvas the canvas to draw on
* @param drawableSpace the space you are allowed to draw on
*/
fun onDraw(canvas: Canvas, drawableSpace: RectF)
/**
* run when manually refreshing the system
*
* this is where the pre-logic is handled to make [onDraw] quicker
*/
fun refresh()
}

View File

@ -0,0 +1,144 @@
package com.dzeio.charts.utils
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import kotlin.math.sqrt
/**
* draw a dotted line
*/
fun Canvas.drawDottedLine(
startX: Float,
startY: Float,
endX: Float,
endY: Float,
spacing: Float,
paint: Paint
) {
//calculate line length
val length = if (endX - startX == 0f) {
// just length of Y
endY - startY
} else if (endY - startY == 0f) {
// just length of X
endX - startX
} else {
// calculate using the Pythagorean theorem
sqrt((startX + endX) * (startX + endX) + (startY + endY) * (startY + endY))
}
val lineCount = (length / spacing).toInt()
val lenX = endX - startX
val lenY = endY - startY
// Log.d("DrawDottedLine", "----------- Start -----------")
// Log.d("DrawDottedLine", "lenX: $lenX, lenY: $lenY")
for (line in 0 until lineCount) {
if (line % 2 == 0) {
continue
}
val sx = lenX / lineCount * line + startX
val sy = lenY / lineCount * line + startY
val ex = lenX / lineCount * (line + 1) + startX
val ey = lenY / lineCount * (line + 1) + startY
// Log.d("DrawDottedLine", "$sx, $sy, $ex, $ey")
this.drawLine(sx, sy, ex, ey, paint)
// line
// total line startX, endX, startY, endY
// total line length
}
}
/**
* A more customizable drawRoundRect function
*/
fun Canvas.drawRoundRect(
left: Float,
top: Float,
right: Float,
bottom: Float,
topLeft: Float,
topRight: Float,
bottomLeft: Float,
bottomRight: Float,
paint: Paint
) {
val maxRound = arrayOf(topLeft, topRight, bottomLeft, bottomRight).maxOf { it }
val width = right - left
val height = bottom - top
// draw first/global rect
drawRoundRect(left, top, right, bottom, maxRound, maxRound, paint)
// top left border
if (topLeft == 0f) {
drawRect(left, top, left + width / 2, top + height / 2, paint)
} else {
drawRoundRect(left, top, left + width / 2, top + height / 2, topLeft, topLeft, paint)
}
// top right border
if (topRight == 0f) {
drawRect(right - width / 2, top, right, top + height / 2, paint)
} else {
drawRoundRect(right - width / 2, top, right, top + height / 2, topRight, topRight, paint)
}
// bottom left border
if (bottomLeft == 0f) {
drawRect(left, bottom - height / 2, left + width / 2, bottom, paint)
} else {
drawRoundRect(
left,
bottom - height / 2,
left + width / 2,
bottom,
bottomLeft,
bottomLeft,
paint
)
}
// bottom right border
if (bottomRight == 0f) {
drawRect(right - width / 2, bottom - height / 2, right, bottom, paint)
} else {
drawRoundRect(
right - width / 2,
bottom - height / 2,
right,
bottom,
bottomRight,
bottomRight,
paint
)
}
}
/**
* A more customizable drawRoundRect function
*/
fun Canvas.drawRoundRect(
rect: RectF,
topLeft: Float,
topRight: Float,
bottomLeft: Float,
bottomRight: Float,
paint: Paint
) {
drawRoundRect(
rect.left,
rect.top,
rect.right,
rect.bottom,
topLeft,
topRight,
bottomLeft,
bottomRight,
paint
)
}

View File

@ -0,0 +1,17 @@
package com.dzeio.charts
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)
}
}

8
local.properties Normal file
View File

@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Jan 10 11:53:10 CET 2023
sdk.dir=/run/media/avior/DATA/Android/SDK

1
sample/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

49
sample/build.gradle.kts Normal file
View File

@ -0,0 +1,49 @@
plugins {
id("com.android.application")
kotlin("android")
}
android {
namespace = "com.dzeio.chartstest"
compileSdk = 33
defaultConfig {
applicationId = "com.dzeio.chartstest"
minSdk = 21
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation(project(":library"))
// Material Design
implementation("com.google.android.material:material:1.7.0")
// Navigation because I don't want to maintain basic transactions and shit
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
}

21
sample/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Charts">
<activity
android:name=".ui.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,19 @@
package com.dzeio.chartstest.ui
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.dzeio.chartstest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}

View File

@ -0,0 +1,166 @@
package com.dzeio.chartstest.ui
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.dzeio.charts.ChartView
import com.dzeio.charts.Entry
import com.dzeio.charts.series.BarSerie
import com.dzeio.charts.series.LineSerie
import com.dzeio.chartstest.databinding.FragmentMainBinding
import com.google.android.material.color.MaterialColors
import kotlin.random.Random
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.chart1.apply {
// setup the Serie
val serie = BarSerie(this)
// utils function to use Material3 auto colors
materielTheme(this, requireView())
// give the serie it's entries
serie.entries = generateRandomDataset(10)
// refresh the Chart
refresh()
}
binding.chart2.apply {
// setup the Serie
val serie = LineSerie(this)
// utils function to use Material3 auto colors
materielTheme(this, requireView())
// give the serie its entries
serie.entries = generateRandomDataset(10)
// refresh the Chart
refresh()
}
binding.chart3.apply {
// setup the Series
val serie1 = BarSerie(this)
val serie2 = LineSerie(this)
// utils function to use Material3 auto colors
materielTheme(this, requireView())
// give the series their entries
serie2.entries = generateRandomDataset(20)
serie1.entries = generateRandomDataset(20).apply {
for (idx in 0 until size) {
val compared = serie2.entries[idx]
val toCompare = this[idx]
if (compared.y > toCompare.y) {
toCompare.color = Color.RED
} else {
toCompare.color = Color.GREEN
}
}
}
// make the lineSerie red
serie2.linePaint.color = Color.WHITE
// refresh the Chart
refresh()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
/**
* Generate a random dataset
*/
private fun generateRandomDataset(size: Int = 100): ArrayList<Entry> {
val dataset: ArrayList<Entry> = arrayListOf()
for (i in 0 until size) {
dataset.add(Entry(
i.toDouble(),
Random.nextInt(0, 100).toFloat()
))
}
return dataset
}
/**
* Apply Material3 theme to a [ChartView]
*/
private fun materielTheme(chart: ChartView, view: View) {
chart.apply {
yAxis.apply {
textLabel.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnPrimaryContainer
)
linePaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnPrimaryContainer
)
goalLinePaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorError
)
}
xAxis.apply {
textPaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnPrimaryContainer
)
}
for (serie in series) {
if (serie is BarSerie) {
serie.apply {
barPaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorPrimary
)
textPaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnPrimary
)
}
} else if (serie is LineSerie) {
serie.apply {
linePaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorPrimary
)
textPaint.color = MaterialColors.getColor(
view,
com.google.android.material.R.attr.colorOnPrimary
)
}
}
}
}
}
}

View File

@ -0,0 +1,30 @@
<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>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<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>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<com.dzeio.charts.ChartView
android:id="@+id/chart1"
android:layout_width="match_parent"
android:layout_height="200dp" />
<com.dzeio.charts.ChartView
android:id="@+id/chart2"
android:layout_marginVertical="16dp"
android:layout_width="match_parent"
android:layout_height="200dp" />
<com.dzeio.charts.ChartView
android:id="@+id/chart3"
android:layout_width="match_parent"
android:layout_height="200dp" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<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" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/MainFragment">
<fragment
android:id="@+id/MainFragment"
android:name="com.dzeio.chartstest.ui.MainFragment"
tools:layout="@layout/fragment_main"/>
</navigation>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name" translatable="false">Dzeio Charts</string>
</resources>

View File

@ -0,0 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Charts" parent="Theme.Material3.DynamicColors.DayNight" />
</resources>

19
settings.gradle Normal file
View File

@ -0,0 +1,19 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Dzeio Charts"
include ':sample'
include ':library'